PublicationContentServlet.java

/*
 * Copyright (C) 2000 - 2025 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.silverpeas.mobile.server.servlets;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.silverpeas.components.gallery.model.MediaPK;
import org.silverpeas.components.gallery.model.Photo;
import org.silverpeas.components.gallery.service.GalleryService;
import org.silverpeas.components.gallery.service.MediaServiceProvider;
import org.silverpeas.components.kmelia.service.KmeliaService;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.core.contribution.attachment.AttachmentServiceProvider;
import org.silverpeas.core.contribution.attachment.model.SimpleDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleDocumentPK;
import org.silverpeas.core.contribution.content.form.*;
import org.silverpeas.core.contribution.content.form.field.FileField;
import org.silverpeas.core.contribution.content.wysiwyg.service.WysiwygController;
import org.silverpeas.core.contribution.publication.model.PublicationDetail;
import org.silverpeas.core.contribution.publication.model.PublicationPK;
import org.silverpeas.core.contribution.template.publication.PublicationTemplate;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager;
import org.silverpeas.core.io.file.ImageResizingProcessor;
import org.silverpeas.core.io.file.SilverpeasFileProcessor;
import org.silverpeas.core.util.ServiceProvider;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.silverpeas.kernel.bundle.ResourceLocator;
import org.silverpeas.kernel.bundle.SettingBundle;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.mobile.server.helpers.FormXMLHelper;

import java.io.*;
import java.net.FileNameMap;
import java.net.URI;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.Properties;

@SuppressWarnings("serial")
public class PublicationContentServlet extends AbstractSilverpeasMobileServlet {
  public static final String USERKEY_ATTRIBUT_NAME = "key";

  private OrganizationController organizationController = OrganizationController.get();
  private static String rootContext = "";
  private SettingBundle mobileSettings = ResourceLocator.getSettingBundle("org.silverpeas.mobile.mobileSettings");

  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws Exception {

    if (rootContext.isEmpty()) {
      try{
        String confPath = System.getenv("SILVERPEAS_HOME") + File.separator + "configuration" + File.separator + "config.properties";
        Properties conf = new Properties();
        InputStream input = new FileInputStream(confPath);
        conf.load(input);
        String port = conf.getProperty("SERVER_PORT");
        if (port == null) port = "8000";
        rootContext= "http://127.0.0.1:"+ port;
        input.close();
      } catch(Exception e) {
        rootContext= "http://127.0.0.1:8000";
      }
    }

    String id = request.getParameter("id");
    String componentId = request.getParameter("componentId");
    String width = request.getParameter("width");
    String ua = request.getHeader("User-Agent");

    response.setContentType("text/html; charset=UTF-8");
    response.setCharacterEncoding("UTF-8");

    response.getWriter().print("<html>");
    response.getWriter().print("<head>");
    response.getWriter().print("<meta http-equiv='content-type' content='text/html;charset=UTF-8' />");

    response.getWriter().print("<link rel=\"stylesheet\" href=\"" + "/silverpeas/spmobile/spmobile.css" + "\">");

    String urlCSS = mobileSettings.getString("styleSheet", "");
    if (!urlCSS.isEmpty()) {
      response.getWriter().print("<link rel=\"stylesheet\" href=\"" + urlCSS + "\">");
    }
    String urlCSSWysiwyg = mobileSettings.getString("wysiwyg.styleSheet", "");
    if (!urlCSSWysiwyg.isEmpty()) {
      response.getWriter().print("<link rel=\"stylesheet\" href=\"" + urlCSSWysiwyg + "\">");
    }

    response.getWriter().print("</head>");
    response.getWriter().print("<body>");

    String html = "";
    if (componentId != null && !componentId.isEmpty() && !componentId.startsWith("kmelia")) {
      if (isXMLTemplateUsed(request, componentId)) {
        PagesContext context = new PagesContext("myForm", "0", getUserInSession(request).getUserPreferences().getLanguage(), false, componentId, "useless");
        context.setObjectId("0");
        context.setBorderPrinted(false);
        PublicationTemplate pubTemplate = getXMLTemplate(request, componentId);
        DataRecord record = getDataRecord(request, componentId);
        html = pubTemplate.getViewForm().toString(context, record);
        html = tranformContent(request, record, html, width);
        response.getWriter().print(html);
      } else {
        html = WysiwygController.loadForReadOnly(componentId, id,
            getUserInSession(request).getUserPreferences().getLanguage());
        displayWysiwyg(html, request, response, componentId, width);
      }
    } else {
      PublicationDetail pub = getKmeliaBm().getPublicationDetail(new PublicationPK(id));
      if (pub.getInfoId().equals("0")) {
        // wysiwyg
        html = pub.getContent().getRenderer().renderView();
        displayWysiwyg(html, request, response, pub.getInstanceId(), width);
      } else {
        // form xml
        displayFormView(response.getWriter(), pub, request, ua, width);
      }
    }


    response.getWriter().print("</body></html>");
    response.getWriter().flush();
  }

  private void displayWysiwyg(String html, HttpServletRequest request, HttpServletResponse response,
      String instanceId, String width) throws IOException {
    html = "<html><body>" + html + "</body></html>";
    Document doc = Jsoup.parse(html);

    Elements body = doc.getElementsByTag("body");
    if (!body.isEmpty()) {
      html = body.first().html();
    }

    Elements images = doc.getElementsByTag("img");
    for (Element img : images) {
      String source = img.attr("src");
      String newSource = source;
      if (source.contains("/silverpeas")) {
        // need to convert in dataurl
        newSource = convertSpImageUrlToDataUrl(source, width);
      }
      img.attr("src", newSource);
    }
    transformHyperlink(doc);

    html = doc.outerHtml();
    writeContainer(response.getWriter(), html);
    response.getWriter().flush();
  }

  private static void transformHyperlink(Document doc) {
    Elements anchors = doc.getElementsByTag("a");
    for (Element a : anchors) {
      String href = a.attr("href");
      if (href.contains("/silverpeas/") && !href.contains(".jsp")) {
        a.attr("href", "#");
        a.attr("onclick", "parent.navigate('" + href + "');");
      }
    }
  }

  private void displayFormView(Writer out, PublicationDetail pub, HttpServletRequest request, String ua, String width)
      throws Exception {

    UserDetail user = getUserInSession(request);
    PublicationTemplate pubTemplate = PublicationTemplateManager.getInstance()
        .getPublicationTemplate(pub.getInstanceId() + ":" + pub.getInfoId());
    DataRecord xmlData = pubTemplate.getRecordSet().getRecord(pub.getId());


    PagesContext xmlContext =
        new PagesContext("myForm", "0", user.getUserPreferences().getLanguage(), false,
            pub.getInstanceId(), "useless");
    xmlContext.setObjectId(pub.getId());
    xmlContext.setDesignMode(false);
    xmlContext.setBorderPrinted(false);
    xmlContext.setContentLanguage(user.getUserPreferences().getLanguage());
    xmlContext.setCreation(false);

    Form xmlForm = pubTemplate.getViewForm();
    String html = xmlForm.toString(xmlContext, xmlData);

    html = tranformContent(request, xmlData, html, width);
    writeContainer(out, html);
    out.flush();
  }

  private String tranformContent(final HttpServletRequest request, final DataRecord xmlData,
      final String html, final String width) throws FormException {
    Document doc = Jsoup.parse(html);

    // transform user fields
    Iterator it = doc.getElementsByAttributeValueStarting("id", "select-user-group-").iterator();
    while (it.hasNext()) {
      Element el = (Element) it.next();

      String htmlField = el.parent().html();
      String fieldName = htmlField.substring(htmlField.indexOf("userInputName\":'")+16);
      fieldName = fieldName.substring(0, fieldName.indexOf("'"));

      //String fieldName = el.id().replace("select-user-group-", "");
      UserDetail u = (UserDetail) xmlData.getField(fieldName).getObjectValue();
      el.text(u.getDisplayedName());
    }

    // use html5 video player
    it = doc.getElementsByAttributeValueStarting("id", "video-player").iterator();
    while (it.hasNext()) {
      Element el = (Element) it.next();
      String fieldName = el.parent().parent().getElementsByTag("label").first().attr("for");
      FileField f = (FileField) xmlData.getField(fieldName);

      //TODO : fix display without desktop session
      String u = "/silverpeas/attached_file/componentId/" + request.getParameter("componentId") + "/attachmentId/"+f.getAttachmentId();
      el.html("<video controls='controls' src='" + u + "' width='100%'></video>");
    }

    Elements images = doc.getElementsByTag("img");
    for (Element img : images) {
      if (img.attr("class").equals("preview-file")) {
        // remove preview for files
        img.remove();
      } else if (img.attr("src").startsWith("/silverpeas/attached_file/componentId/")) {
        // convert url to dataurl
        String data = img.attr("src");
        data = convertImageAttachmentUrl(data, data, width);
        img.attr("src", data);
      }
    }

    transformHyperlink(doc);
    
    boolean includeJs = getSettings().getBoolean("allow.js.in.content");
    if (!includeJs) {
      // remove all scripts
      Elements scripts = doc.getElementsByTag("script");
      for (Element script : scripts) {
        script.remove();
      }
    }

    return doc.outerHtml();
  }

  private void writeContainer(Writer out, String html) throws IOException {
    out.write("<div id='gesture-area'>");
    out.write("<div id='scale-element'>");
    out.write("<div id='real-element'>");
    out.write(html);
    out.write("</div>");
    out.write("</div>");
    out.write("</div>");
  }

  private String convertSpImageUrlToDataUrl(String url, String width) {
    String data = url;
    if (url.contains("GalleryInWysiwyg")) {
      try {
        String instanceId = url.substring(url.indexOf("ComponentId") + "ComponentId".length() + 1);
        instanceId = instanceId.substring(0, instanceId.indexOf("&"));
        String imageId = url.substring(url.indexOf("ImageId") + "ImageId".length() + 1);
        imageId = imageId.substring(0, imageId.indexOf("&"));
        Photo photo = getGalleryService().getPhoto(new MediaPK(imageId));
        String[] rep = {"image" + imageId};

        String path = FileRepositoryManager.getAbsolutePath(instanceId, rep);
        File f = new File(path + photo.getFileName());
        f = resizeImage(f, width + "x");
        FileInputStream is = new FileInputStream(f);
        byte[] binaryData = new byte[(int) f.length()];
        is.read(binaryData);
        is.close();
        data = "data:" + photo.getFileMimeType() + ";base64," +
            new String(Base64.encodeBase64(binaryData));
      } catch (Exception e) {
        SilverLogger.getLogger(this)
            .error("PublicationContentServlet.convertSpImageUrlToDataUrl", e);
      }
    } else if (url.contains("attachmentId")) {
      data = convertImageAttachmentUrl(url, data, width);
    } else {
      try {
        if (url.startsWith("/silverpeas")) {
          url = rootContext + url;
        }
        URLConnection connection = new URI(url).toURL().openConnection();
        connection.connect();
        String contentType = connection.getContentType();
        if (contentType == null) {
          FileNameMap fileNameMap = URLConnection.getFileNameMap();
          contentType = fileNameMap.getContentTypeFor(url);
        }

        byte[] binaryData = new byte[(int) connection.getInputStream().available()];
        connection.getInputStream().read(binaryData);
        data = "data:" + contentType + ";base64," + new String(Base64.encodeBase64(binaryData));
      } catch (Exception e) {
        SilverLogger.getLogger(this)
            .error("PublicationContentServlet.convertImageUrlToDataUrl", e);
        // If can't connect to url, return the url without change
      }
    }
    return data;
  }

  private String convertImageAttachmentUrl(final String url, String data, String width) {

    String attachmentId = url.substring(url.indexOf("attachmentId/") + "attachmentId/".length());
    attachmentId = attachmentId.substring(0, attachmentId.indexOf("/"));
    String componentId = "";

    if (url.indexOf("kmelia") != -1) {
      componentId = url.substring(url.indexOf("kmelia"));
    } else if (url.indexOf("quickinfo") != -1) {
      componentId = url.substring(url.indexOf("quickinfo"));
    } else if (url.indexOf("webPages") != -1) {
      componentId = url.substring(url.indexOf("webPages"));
    }
    componentId = componentId.substring(0, componentId.indexOf("/"));

    SimpleDocument attachment = AttachmentServiceProvider.getAttachmentService()
        .searchDocumentById(new SimpleDocumentPK(attachmentId, componentId), null);

    try {
      File f = new File(attachment.getAttachmentPath());


      f = resizeImage(f, width + "x");

      FileInputStream is = new FileInputStream(f);
      byte[] binaryData = new byte[(int) f.length()];
      is.read(binaryData);
      is.close();
      data = "data:" + attachment.getContentType() + ";base64," +
          new String(Base64.encodeBase64(binaryData));
    } catch (Exception e) {
      SilverLogger.getLogger(this)
          .error("PublicationContentServlet.convertSpImageUrlToDataUrl", e);
    }
    return data;
  }

  private File resizeImage(final File originalFile, String size) {
    ImageResizingProcessor processor = ServiceProvider.getService(ImageResizingProcessor.class);
    final String path;
    String askedPath = pathForOriginalImageSize(originalFile, originalFile.getName(), size);
    path = processor.processBefore(askedPath, SilverpeasFileProcessor.ProcessingContext.GETTING);
    return new File(path);
  }

  private String pathForOriginalImageSize(File originalImage,String name, String size) {
    return originalImage.getParent() + File.separator + size + File.separator + name;
  }


  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    try {
      checkUserInSession(request, response);
      processRequest(request, response);
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
    }
  }

  private KmeliaService getKmeliaBm() {
    return KmeliaService.get();
  }

  private GalleryService getGalleryService() throws Exception {
    return MediaServiceProvider.getMediaService();
  }

  private String getUsedXMLTemplate(HttpServletRequest request, String instanceId)
      throws Exception {
    return FormXMLHelper.getUsedXMLTemplate(request, instanceId);
  }

  public boolean isXMLTemplateUsed(HttpServletRequest request, String instanceId) throws Exception {
    return FormXMLHelper.isXMLTemplateUsed(request, instanceId);
  }
  protected String getUserKeyInSession(HttpServletRequest request) {
    return (String) request.getSession().getAttribute(USERKEY_ATTRIBUT_NAME);
  }

  private PublicationTemplate getXMLTemplate(HttpServletRequest request, String instanceId)
      throws Exception {
    return FormXMLHelper.getXMLTemplate(request, instanceId);
  }

  private void registerXMLForm(HttpServletRequest request, String instanceId) throws Exception {
   FormXMLHelper.registerXMLForm(request, instanceId);
  }


  private String getUsedXMLTemplateShortname(HttpServletRequest request, String instanceId)
      throws Exception {
    return FormXMLHelper.getUsedXMLTemplateShortname(request, instanceId);
  }

  private DataRecord getDataRecord(HttpServletRequest request, String instanceId) throws Exception {
    PublicationTemplate pubTemplate = getXMLTemplate(request, instanceId);
    RecordSet recordSet = pubTemplate.getRecordSet();
    DataRecord data = recordSet.getRecord("0");
    if (data == null) {
      data = recordSet.getEmptyRecord();
      data.setId("0");
    }
    return data;
  }

}