KmeliaSessionController.java

/*
 * Copyright (C) 2000 - 2026 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 <https://www.gnu.org/licenses/>.
 */
package org.silverpeas.components.kmelia.control;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FileUtils;
import org.owasp.encoder.Encode;
import org.silverpeas.components.kmelia.FileImport;
import org.silverpeas.components.kmelia.InstanceParameters;
import org.silverpeas.components.kmelia.KmeliaCopyDetail;
import org.silverpeas.components.kmelia.KmeliaPasteDetail;
import org.silverpeas.components.kmelia.KmeliaPublicationHelper;
import org.silverpeas.components.kmelia.SearchContext;
import org.silverpeas.components.kmelia.export.ExportFileNameProducer;
import org.silverpeas.components.kmelia.export.KmeliaPublicationExporter;
import org.silverpeas.components.kmelia.model.KmeliaPublication;
import org.silverpeas.components.kmelia.model.KmeliaPublicationSort;
import org.silverpeas.components.kmelia.model.KmeliaRuntimeException;
import org.silverpeas.components.kmelia.model.TopicDetail;
import org.silverpeas.components.kmelia.model.TopicSearch;
import org.silverpeas.components.kmelia.search.KmeliaSearchServiceProvider;
import org.silverpeas.components.kmelia.service.KmeliaHelper;
import org.silverpeas.components.kmelia.service.KmeliaNodeSimulationElementLister;
import org.silverpeas.components.kmelia.service.KmeliaPublicationBatchSimulationElementLister;
import org.silverpeas.components.kmelia.service.KmeliaPublicationSimulationElementLister;
import org.silverpeas.components.kmelia.service.KmeliaService;
import org.silverpeas.components.kmelia.service.KmeliaXmlFormUpdateContext;
import org.silverpeas.core.ActionType;
import org.silverpeas.core.ResourceReference;
import org.silverpeas.kernel.SilverpeasRuntimeException;
import org.silverpeas.core.admin.ProfiledObjectId;
import org.silverpeas.core.admin.ProfiledObjectType;
import org.silverpeas.core.admin.component.model.ComponentInst;
import org.silverpeas.core.admin.component.model.GlobalContext;
import org.silverpeas.core.admin.service.AdminController;
import org.silverpeas.core.admin.space.SpaceInstLight;
import org.silverpeas.core.admin.user.model.Group;
import org.silverpeas.core.admin.user.model.ProfileInst;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.core.clipboard.ClipboardException;
import org.silverpeas.core.clipboard.ClipboardSelection;
import org.silverpeas.core.contribution.attachment.AttachmentServiceProvider;
import org.silverpeas.core.contribution.attachment.model.Attachments;
import org.silverpeas.core.contribution.attachment.model.DocumentType;
import org.silverpeas.core.contribution.attachment.model.SimpleDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleDocumentPK;
import org.silverpeas.core.contribution.attachment.process.huge.AttachmentHugeProcessManager;
import org.silverpeas.core.contribution.content.form.DataRecord;
import org.silverpeas.core.contribution.content.form.Field;
import org.silverpeas.core.contribution.content.form.Form;
import org.silverpeas.core.contribution.content.form.PagesContext;
import org.silverpeas.core.contribution.content.form.RecordSet;
import org.silverpeas.core.contribution.content.wysiwyg.service.WysiwygController;
import org.silverpeas.core.contribution.converter.DocumentFormat;
import org.silverpeas.core.contribution.model.LocalizedContribution;
import org.silverpeas.core.contribution.publication.datereminder.PublicationNoteReference;
import org.silverpeas.core.contribution.publication.model.CompletePublication;
import org.silverpeas.core.contribution.publication.model.Location;
import org.silverpeas.core.contribution.publication.model.PublicationDetail;
import org.silverpeas.core.contribution.publication.model.PublicationPK;
import org.silverpeas.core.contribution.publication.model.PublicationSelection;
import org.silverpeas.core.contribution.publication.model.ValidationStep;
import org.silverpeas.core.contribution.publication.service.PublicationService;
import org.silverpeas.core.contribution.publication.subscription.PublicationAliasSubscriptionResource;
import org.silverpeas.core.contribution.publication.subscription.PublicationSubscriptionResource;
import org.silverpeas.core.contribution.template.publication.PublicationTemplate;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateException;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateImpl;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager;
import org.silverpeas.core.datereminder.exception.DateReminderException;
import org.silverpeas.core.datereminder.persistence.DateReminderDetail;
import org.silverpeas.core.datereminder.persistence.PersistentResourceDateReminder;
import org.silverpeas.core.datereminder.persistence.service.DateReminderServiceProvider;
import org.silverpeas.core.datereminder.persistence.service.PersistentDateReminderService;
import org.silverpeas.core.i18n.I18NHelper;
import org.silverpeas.core.importexport.ExportDescriptor;
import org.silverpeas.core.importexport.attachment.AttachmentImportExport;
import org.silverpeas.core.importexport.model.ImportExportException;
import org.silverpeas.core.importexport.report.ComponentReport;
import org.silverpeas.core.importexport.report.ImportReport;
import org.silverpeas.core.importexport.report.MassiveReport;
import org.silverpeas.core.importexport.report.UnitReport;
import org.silverpeas.core.index.indexing.model.FieldDescription;
import org.silverpeas.core.index.indexing.model.IndexManager;
import org.silverpeas.core.index.search.model.MatchingIndexEntry;
import org.silverpeas.core.index.search.model.QueryDescription;
import org.silverpeas.core.io.media.image.thumbnail.ThumbnailSettings;
import org.silverpeas.core.io.upload.UploadedFile;
import org.silverpeas.core.node.coordinates.model.Coordinate;
import org.silverpeas.core.node.model.NodeDetail;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.core.node.model.NodeSelection;
import org.silverpeas.core.node.service.NodeService;
import org.silverpeas.core.pdc.pdc.model.ClassifyPosition;
import org.silverpeas.core.pdc.pdc.model.PdcClassification;
import org.silverpeas.core.pdc.pdc.model.PdcPosition;
import org.silverpeas.core.pdc.pdc.service.PdcClassificationService;
import org.silverpeas.core.pdc.pdc.service.PdcManager;
import org.silverpeas.core.process.annotation.SimulationActionProcessProcessor;
import org.silverpeas.core.security.authorization.AccessControlContext;
import org.silverpeas.core.security.authorization.NodeAccessControl;
import org.silverpeas.core.security.authorization.PublicationAccessControl;
import org.silverpeas.core.silverstatistics.access.model.HistoryObjectDetail;
import org.silverpeas.core.silverstatistics.access.service.StatisticService;
import org.silverpeas.core.subscription.SubscriptionResource;
import org.silverpeas.core.subscription.service.ComponentSubscriptionResource;
import org.silverpeas.core.subscription.service.NodeSubscriptionResource;
import org.silverpeas.core.template.SilverpeasTemplate;
import org.silverpeas.core.template.SilverpeasTemplateFactory;
import org.silverpeas.core.util.*;
import org.silverpeas.core.util.error.SilverpeasTransverseErrorUtil;
import org.silverpeas.core.util.file.FileFolderManager;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.silverpeas.core.util.file.FileUploadUtil;
import org.silverpeas.core.util.file.FileUtil;
import org.silverpeas.kernel.bundle.LocalizationBundle;
import org.silverpeas.kernel.bundle.ResourceLocator;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController;
import org.silverpeas.core.web.mvc.controller.ComponentContext;
import org.silverpeas.core.web.mvc.controller.MainSessionController;
import org.silverpeas.core.web.mvc.webcomponent.WebMessager;
import org.silverpeas.core.web.selection.Selection;
import org.silverpeas.core.web.selection.SelectionUsersGroups;
import org.silverpeas.core.web.subscription.SubscriptionContext;
import org.silverpeas.core.web.subscription.SubscriptionResourcePath;
import org.silverpeas.core.webapi.pdc.PdcClassificationEntity;
import org.silverpeas.kernel.SilverpeasException;
import org.silverpeas.kernel.util.Pair;
import org.silverpeas.kernel.util.StringUtil;

import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.silverpeas.components.kmelia.control.KmeliaSessionController.CLIPBOARD_STATE.*;
import static org.silverpeas.components.kmelia.export.KmeliaPublicationExporter.*;
import static org.silverpeas.core.admin.component.model.ComponentInst.getComponentLocalId;
import static org.silverpeas.core.cache.service.CacheAccessorProvider.getSessionCacheAccessor;
import static org.silverpeas.core.cache.service.VolatileIdentifierProvider.newVolatileIntegerIdentifierOn;
import static org.silverpeas.core.contribution.attachment.AttachmentService.VERSION_MODE;
import static org.silverpeas.core.index.search.SearchEngineProvider.getSearchEngine;
import static org.silverpeas.core.pdc.pdc.model.PdcClassification.NONE_CLASSIFICATION;
import static org.silverpeas.core.pdc.pdc.model.PdcClassification.aPdcClassificationOfContent;

public class KmeliaSessionController extends AbstractComponentSessionController
    implements ExportFileNameProducer {
  private static final long serialVersionUID = -1986747239669483105L;

  /**
   * The different export formats the KmeliaPublicationExporter should support.
   */
  private static final String EXPORT_FORMATS = "kmelia.export.formats.active";
  /**
   * All the formats that are available for the export of publications.
   */
  private static final String[] AVAILABLE_EXPORT_FORMATS = {"zip", "pdf", "odt", "doc"};
  private static final int DEFAULT_NBPUBLIS_PER_PAGE = 10;
  private static final String INACCESSIBLE_ATTACHMENT = "liaisonInaccessible";
  private static final String KMELIA = "kmelia";
  private static final String PUBLICATION = "Publication";
  private static final String USELESS = "useless";

  private String currentFolderId = NodePK.ROOT_NODE_ID;
  private KmeliaPublication sessionPublication = null;
  private KmeliaPublication sessionClone = null;
  // sessionPath html link with <a href="">
  private String sessionPath = null;
  // sessionPathString html string only
  private String sessionPathString = null;
  private TopicDetail sessionTopicToLink = null;
  private boolean sessionOwner = false;
  // Specific Kmax
  private List<String> sessionCombination = null;
  // Specific Kmax
  private String sessionTimeCriteria = null;
  private KmeliaPublicationSort sortValue;
  private int rang = 0;
  public static final String TAB_PREVIEW = "tabpreview";
  public static final String TAB_HEADER = "tabheader";
  public static final String TAB_CONTENT = "tabcontent";
  public static final String TAB_COMMENT = "tabcomments";
  public static final String TAB_ATTACHMENTS = "tabattachments";
  public static final String TAB_SEE_ALSO = "tabseealso";
  public static final String TAB_ACCESS_PATHS = "tabaccesspaths";
  public static final String TAB_READER_LIST = "tabreaderslist";
  // For import files
  public static final String UNITARY_IMPORT_MODE = "0";
  public static final String MASSIVE_IMPORT_MODE_ONE_PUBLICATION = "1";
  public static final String MASSIVE_IMPORT_MODE_MULTI_PUBLICATIONS = "2";
  // pagination de la liste des publications
  private int indexOfFirstPubToDisplay = 0;
  private int nbPublicationsPerPage = DEFAULT_NBPUBLIS_PER_PAGE;
  // Specific for Kmax
  private List<Integer> timeAxis = null;
  private List<String> currentCombination = null;
  private boolean isKmaxMode = false;
  // i18n
  private String currentLanguage = null;
  private String cachedCurrentLanguage = null;
  boolean isDragAndDropEnableByUser = false;
  boolean componentManageable = false;
  private final List<PublicationPK> selectedPublicationPKs = new ArrayList<>();
  private boolean customPublicationTemplateUsed = false;
  private String customPublicationTemplateName = null;
  private SearchContext searchContext = null;

  // select/deselect all
  private boolean allPublicationsListSelected = false;

  public KmeliaSessionController(MainSessionController mainSessionCtrl, ComponentContext context) {
    super(mainSessionCtrl, context, "org.silverpeas.kmelia.multilang.kmeliaBundle",
        "org.silverpeas.kmelia.settings.kmeliaIcons",
        "org.silverpeas.kmelia.settings.kmeliaSettings");
    init();
  }

  public static List<String> getLanguagesOfAttachments(ResourceReference resourceReference) {
    List<String> languages = new ArrayList<>();
    for (String availableLanguage : I18NHelper.getAllSupportedLanguages()) {
      List<SimpleDocument> attachments = AttachmentServiceProvider.getAttachmentService()
          .listDocumentsByForeignKeyAndType(resourceReference, DocumentType.attachment,
              availableLanguage);
      for (SimpleDocument attachment : attachments) {
        if (availableLanguage.equalsIgnoreCase(attachment.getLanguage())) {
          languages.add(availableLanguage);
          break;
        }
      }
    }
    return languages;
  }

  public boolean isPasteNodeAllowed() throws ClipboardException {
    SilverLogger.getLogger(this).debug("isPasteNodeAllowed() nbSelectedObjects = {0}",
        getClipboardSelectedObjects().size());
    return getClipboardSelectedObjects().stream().anyMatch(s -> s.isDataFlavorSupported(NodeSelection.NodeDetailFlavor));
  }

  public boolean isPastePublicationAllowed(boolean isRoot) throws ClipboardException {
    SilverLogger.getLogger(this).debug("isPastePublicationAllowed() isRoot = {0} - " +
 "nbSelectedObjects = {1}", isRoot, getClipboardSelectedObjects().size());
    return getClipboardSelectedObjects().stream().anyMatch(s -> s.isDataFlavorSupported(
        PublicationSelection.PublicationDetailFlavor)) && (isPublicationAllowed(isRoot));
  }

  private void init() {
    sortValue = new KmeliaPublicationSort(getDefaultSortValue());

    // Remove all data store by this SessionController
    removeSessionObjects();
    currentLanguage = getLanguage();
    if (StringUtil.getBooleanValue(getSettings().getString("massiveDragAndDropAllowed"))) {
      isDragAndDropEnableByUser = isDragAndDropEnableByUser();
    }
    componentManageable = ResourceLocator.getGeneralSettingBundle().getBoolean(
        "AdminFromComponentEnable", true);
    if (componentManageable) {
      componentManageable =
          getOrganisationController().isComponentManageable(getComponentId(), getUserId());
    }
    // check if this instance use a specific template of publication
    SilverpeasTemplate template = SilverpeasTemplateFactory.createSilverpeasTemplateOnComponents();
    customPublicationTemplateName = "publication_" + getComponentId();
    customPublicationTemplateUsed =
        template.isCustomTemplateExists(KMELIA, customPublicationTemplateName);
    cacheDirectlyPublicationsListInSession(Collections.emptyList());

    nbPublicationsPerPage = getSettings().getInteger("NbPublicationsParPage", 10);
    String parameterValue = getComponentParameterValue("nbPubliPerPage");
    if (StringUtil.isInteger(parameterValue)) {
      nbPublicationsPerPage = Integer.parseInt(parameterValue);
    }
  }

  private int getDefaultSortValue() {
    String defaultSortValue = getComponentParameterValue("publicationSort");
    if (!StringUtil.isDefined(defaultSortValue)) {
      defaultSortValue = getSettings().getString("publications.sort.default", "2");
    }
    return Integer.parseInt(defaultSortValue);
  }

  public boolean isKmaxMode() {
    return isKmaxMode;
  }

  public KmeliaSessionController setKmaxMode(final boolean kmaxMode) {
    isKmaxMode = kmaxMode;
    return this;
  }

  public KmeliaService getKmeliaService() {
    return KmeliaService.get();
  }

  public StatisticService getStatisticService() {
    return StatisticService.get();
  }

  /**
   * Gets a business service of dateReminder.
   *
   * @return a DefaultDateReminderService instance.
   */
  protected PersistentDateReminderService getDateReminderService() {
    return DateReminderServiceProvider.getDateReminderService();
  }

  public int getNbPublicationsOnRoot() {
    int nbPublicationsOnRoot = 0;
    String parameterValue = getComponentParameterValue("nbPubliOnRoot");
    if (StringUtil.isDefined(parameterValue)) {
      nbPublicationsOnRoot = Integer.parseInt(parameterValue);
    } else {
      if (KmeliaHelper.isKmelia(getComponentId())) {
        // lecture du properties
        nbPublicationsOnRoot = getSettings().getInteger("HomeNbPublications", 15);
      }
    }
    return nbPublicationsOnRoot;
  }

  public int getNbPublicationsPerPage() {
    return nbPublicationsPerPage;
  }

  public void setNbPublicationsPerPage(int nb) {
    nbPublicationsPerPage = nb;
  }

  public boolean isDraftVisibleWithCoWriting() {
    return getSettings().getBoolean("draftVisibleWithCoWriting", false);
  }

  public boolean isTreeStructure() {
    return KmeliaPublicationHelper.isTreeEnabled(getComponentId());
  }

  public boolean isTreeviewUsed() {
    String param = getComponentParameterValue("istree");
    if (!StringUtil.isDefined(param)) {
      return true;
    }
    return "0".equals(param);
  }

  public boolean isPdcUsed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("usepdc"));
  }

  public boolean isDraftEnabled() {
    return StringUtil.getBooleanValue(getComponentParameterValue("draft"));
  }

  public boolean isTopicManagementDelegated() {
    return StringUtil.getBooleanValue(getComponentParameterValue("delegatedTopicManagement"));
  }

  public boolean isAuthorUsed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("useAuthor"));
  }

  public boolean isReminderUsed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("useReminder"));
  }

  public boolean isStatisticAllowed() {
    boolean statisticEnabled = getSettings().getBoolean("kmelia.stats.enable", false);
    return statisticEnabled && !isToolbox() &&
        (getHighestSilverpeasUserRole().isGreaterThanOrEquals(SilverpeasRole.PUBLISHER) ||
            getSilverpeasUserRoles().contains(SilverpeasRole.SUPERVISOR));
  }

  public boolean openSingleAttachmentAutomatically() {
    return StringUtil.getBooleanValue(getComponentParameterValue("openSingleAttachment"));
  }

  public boolean isImportFileAllowed() {
    String parameterValue = getComponentParameterValue("importFiles");
    if (!StringUtil.isDefined(parameterValue)) {
      return false;
    } else {
      return "1".equalsIgnoreCase(parameterValue) || "3".equalsIgnoreCase(parameterValue);
    }
  }

  public boolean isImportFilesAllowed() {
    String parameterValue = this.getComponentParameterValue("importFiles");
    if (parameterValue == null || parameterValue.isEmpty()) {
      return false;
    } else {
      return "2".equalsIgnoreCase(parameterValue) || "3".equalsIgnoreCase(parameterValue);
    }
  }

  public boolean isExportApplicationAllowed(SilverpeasRole greatestUserRole) {
    String value = this.getComponentParameterValue("exportApplication");
    if ("1".equals(value)) {
      return greatestUserRole.isGreaterThanOrEquals(SilverpeasRole.ADMIN);
    } else if ("2".equals(value)) {
      return greatestUserRole.isGreaterThanOrEquals(SilverpeasRole.WRITER);
    }
    return "3".equals(value);
  }

  public boolean isExportTopicAllowed(SilverpeasRole greatestUserRole) {
    String value = this.getComponentParameterValue("exportTopic");
    if ("1".equals(value)) {
      return greatestUserRole.isGreaterThanOrEquals(SilverpeasRole.ADMIN);
    } else if ("2".equals(value)) {
      return greatestUserRole.isGreaterThanOrEquals(SilverpeasRole.WRITER);
    }
    return "3".equals(value);
  }

  public boolean isExportPublicationAllowed(SilverpeasRole greatestUserRole) {
    String value = this.getComponentParameterValue("exportPublication");
    if ("1".equals(value)) {
      return greatestUserRole.isGreaterThanOrEquals(SilverpeasRole.ADMIN);
    } else if ("2".equals(value)) {
      return greatestUserRole.isGreaterThanOrEquals(SilverpeasRole.WRITER);
    }
    return "3".equals(value);
  }


  public boolean isMassiveDragAndDropAllowed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("massiveDragAndDrop"));
  }

  public boolean isPublicationAlwaysVisibleEnabled() {
    return StringUtil.getBooleanValue(getComponentParameterValue("publicationAlwaysVisible"));
  }

  public boolean displayNbPublis() {
    return StringUtil
        .getBooleanValue(getComponentParameterValue(InstanceParameters.displayNbItemsOnFolders));
  }

  public boolean isRightsOnTopicsEnabled() {
    return StringUtil.
        getBooleanValue(getComponentParameterValue(InstanceParameters.rightsOnFolders));
  }

  private boolean isRightsOnTopicsEnabled(String componentId) {
    return StringUtil.getBooleanValue(getOrganisationController()
        .getComponentParameterValue(componentId, InstanceParameters.rightsOnFolders));
  }

  public boolean attachmentsInPubList() {
    return StringUtil.getBooleanValue(getComponentParameterValue("attachmentsInPubList"));
  }

  public boolean isPublicationIdDisplayed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("codification"));
  }

  public boolean isSuppressionOnlyForAdmin() {
    return StringUtil.getBooleanValue(getComponentParameterValue("suppressionOnlyForAdmin"));
  }

  public boolean isSuppressionAllowed(String profile) {
    SilverpeasRole role = SilverpeasRole.fromString(profile);
    boolean modifPrivilege = role == SilverpeasRole.ADMIN || role == SilverpeasRole.PUBLISHER ||
        role == SilverpeasRole.SUPERVISOR;
    return (!isSuppressionOnlyForAdmin() && modifPrivilege)
        || (isSuppressionOnlyForAdmin() && role == SilverpeasRole.ADMIN);
  }

  public boolean isContentEnabled() {
    String parameterValue = getComponentParameterValue("tabContent");
    if (!StringUtil.isDefined(parameterValue)) {
      return false;
    }
    return StringUtil.getBooleanValue(parameterValue);
  }

  public boolean isSeeAlsoEnabled() {
    return StringUtil.getBooleanValue(getComponentParameterValue("tabSeeAlso"));
  }

  public boolean isPublicationRatingAllowed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("publicationRating"));
  }

  public boolean showUserNameInList() {
    return getSettings().getBoolean("showUserNameInList", true);
  }

  public boolean isLastVisitorsEnabled() {
    String parameterValue = getComponentParameterValue("tabLastVisitors");
    if (!StringUtil.isDefined(parameterValue)) {
      return true;
    }
    return StringUtil.getBooleanValue(parameterValue);
  }

  public List<String> getInvisibleTabs() {
    List<String> invisibleTabs = new ArrayList<>(0);

    if (!isContentEnabled()) {
      invisibleTabs.add(TAB_CONTENT);
    }

    if (isToolbox()) {
      invisibleTabs.add(TAB_PREVIEW);
    }

    String parameterValue = this.getComponentParameterValue("tabAttachments");
    if (!isToolbox() && StringUtil.isDefined(parameterValue) &&
        !StringUtil.getBooleanValue(parameterValue)) {
      // attachments tab is always visible with toolbox
      invisibleTabs.add(TAB_ATTACHMENTS);
    }

    if (!isSeeAlsoEnabled()) {
      invisibleTabs.add(TAB_SEE_ALSO);
    }

    parameterValue = this.getComponentParameterValue("tabAccessPaths");
    if (StringUtil.isDefined(parameterValue) &&
        !StringUtil.getBooleanValue(parameterValue)) {
      invisibleTabs.add(TAB_ACCESS_PATHS);
    }

    parameterValue = this.getComponentParameterValue("tabReadersList");
    if (StringUtil.isDefined(parameterValue) &&
        !StringUtil.getBooleanValue(parameterValue)) {
      invisibleTabs.add(TAB_READER_LIST);
    }

    parameterValue = this.getComponentParameterValue("tabComments");
    if (!StringUtil.isDefined(parameterValue)) {
      invisibleTabs.add(TAB_COMMENT);
    } else {
      if (!StringUtil.getBooleanValue(parameterValue)) {
        invisibleTabs.add(TAB_COMMENT);
      }
    }

    return invisibleTabs;
  }

  /**
   * Generates a document in the specified format from the specified publication.
   *
   * @param inFormat the format of the document to generate.
   * @param fromPubId the unique identifier of the publication from which the document will be
   * generated.
   * @return the generated document as a File instance.
   */
  public File generateDocument(final DocumentFormat inFormat, String fromPubId) {

    if (!isFormatSupported(inFormat.name())) {
      throw new KmeliaRuntimeException("Document format " + inFormat.name() + " not yet supported");
    }
    File document = null;
    if (fromPubId != null) {
      try {
        KmeliaPublication publication = KmeliaPublication.withPK(
            new PublicationPK(fromPubId, getComponentId()), getCurrentFolderPK());
        String fileName = getPublicationExportFileName(publication, getLanguage());
        document = new File(FileRepositoryManager.getTemporaryPath() + fileName + "." + inFormat.
            name());
        FileOutputStream output = new FileOutputStream(document);
        ExportDescriptor descriptor = ExportDescriptor.withOutputStream(output).
            withParameter(EXPORT_FOR_USER, getUserDetail()).
            withParameter(EXPORT_LANGUAGE, getLanguage()).
            withParameter(EXPORT_TOPIC, getCurrentFolderId()).
            inMimeType(inFormat.name());
        aKmeliaPublicationExporter().exports(descriptor, () -> publication);
      } catch (Exception ex) {
        SilverLogger.getLogger(this).error("Publication export failure", ex);
        if (document != null) {
          FileUtils.deleteQuietly(document);
        }
        throw new KmeliaRuntimeException(ex);
      }
    }
    return document;
  }

  /**
   * Gets a new exporter of Kmelia publications.
   *
   * @return a KmeliaPublicationExporter instance.
   */
  public static KmeliaPublicationExporter aKmeliaPublicationExporter() {
    return ServiceProvider.getService(KmeliaPublicationExporter.class);
  }

  @Override
  public SilverpeasRole getHighestSilverpeasUserRole() {
    SilverpeasRole userRole = SilverpeasRole.fromString(getProfile());
    return userRole != null ? userRole : super.getHighestSilverpeasUserRole();
  }

  public String getProfile() {
    return getUserTopicProfile();
  }

  public String getUserTopicProfile() {
    return getUserTopicProfile(null);
  }

  public String getUserTopicProfile(String id) {
    String nodeId = id;
    if (!StringUtil.isDefined(id)) {
      nodeId = getCurrentFolderId();
      if (!KmeliaHelper.isSpecialFolder(nodeId)) {
        try {
          // check that current node still exists
          getKmeliaService().getNodeHeader(nodeId, getComponentId());
        } catch (Exception e) {
          SilverLogger.getLogger(this).warn(e);
          setCurrentFolderId(NodePK.ROOT_NODE_ID, true);
          nodeId = NodePK.ROOT_NODE_ID;
        }
      }
    }
    return getKmeliaService().getUserTopicProfile(getNodePK(nodeId), getUserId());
  }

  public List<String> getUserIdsOfTopic() {
    return getKmeliaService().getUserIdsOfFolder(getCurrentFolderPK());
  }

  public boolean isCurrentTopicAvailable() {
    if (isRightsOnTopicsEnabled()) {
      if (KmeliaHelper.isSpecialFolder(getCurrentFolderId())) {
        return true;
      }
      return NodeAccessControl.get().isUserAuthorized(getUserId(), getCurrentFolderPK());
    }
    return true;
  }

  public boolean isUserComponentAdmin() {
    return SilverpeasRole.ADMIN.isInRole(KmeliaHelper.getSilverpeasRole(getSilverpeasUserRoles()));
  }

  /*
   * Topic management
   */
  public synchronized TopicDetail getTopic(String id) {
    return getTopic(id, true);
  }

  public synchronized TopicDetail getTopic(String id, boolean resetSessionItems) {
    if (resetSessionItems) {
      setSessionPublication(null);
    }
    if (!id.equals(getCurrentFolderId())) {
      indexOfFirstPubToDisplay = 0;
    }

    TopicDetail currentTopic;
    if (isUserComponentAdmin()) {
      currentTopic =
          getKmeliaService().goTo(getNodePK(id), getUserId(), isTreeStructure(), "admin", false);
    } else {
      currentTopic = getKmeliaService()
          .goTo(getNodePK(id), getUserId(), isTreeStructure(), getUserTopicProfile(id),
              isRightsOnTopicsEnabled());
    }

    if (displayNbPublis()) {
      prepareForPublicationNbDisplaying(currentTopic);
    }
    if (resetSessionItems) {
      setSessionTopic(currentTopic);
    }
    applyVisibilityFilter();
    return currentTopic;
  }

  private void prepareForPublicationNbDisplaying(final TopicDetail currentTopic) {
    List<NodeDetail> treeview = getTreeview("0");
    // set nb objects of current root
    currentTopic.getNodeDetail().setNbObjects(treeview.get(0).getNbObjects());
    // set nb objects of children
    Collection<NodeDetail> children = currentTopic.getNodeDetail().getChildrenDetails();
    for (NodeDetail node : children) {
      if (node != null) {
        int index = treeview.indexOf(node);
        if (index != -1) {
          NodeDetail nodeTreeview = treeview.get(index);
          if (nodeTreeview != null) {
            node.setNbObjects(nodeTreeview.getNbObjects());
          }
        }
      }
    }
  }

  public List<NodeDetail> getTreeview(String nodeId) {
    if (isUserComponentAdmin()) {
      return getKmeliaService().getTreeview(getNodePK(nodeId), "admin", isCoWritingEnable(),
          isDraftVisibleWithCoWriting(), getUserId(), displayNbPublis(), false);
    } else {
      return getKmeliaService().getTreeview(getNodePK(nodeId), getProfile(), isCoWritingEnable(),
          isDraftVisibleWithCoWriting(), getUserId(), displayNbPublis(), isRightsOnTopicsEnabled());
    }
  }

  public synchronized TopicDetail getBestTopicDetailsOfPublication(String pubId) {
    final TopicDetail currentTopic = getKmeliaService()
        .getBestTopicDetailOfPublicationForUser(getPublicationPK(pubId), isTreeStructure(),
            getUserId());
    setSessionTopic(currentTopic);
    applyVisibilityFilter();
    return currentTopic;
  }

  public synchronized void flushTrashCan() {

    TopicDetail td = getKmeliaService()
        .goTo(getNodePK(NodePK.BIN_NODE_ID), getUserId(), false, getUserTopicProfile("1"),
            isRightsOnTopicsEnabled());
    setSessionTopic(td);
    Collection<KmeliaPublication> pds = td.getKmeliaPublications();

    for (final KmeliaPublication pd : pds) {
      String theId = pd.getDetail()
          .getPK()
          .getId();

      deletePublication(theId);
    }
    indexOfFirstPubToDisplay = 0;
  }

  /**
   * Update the specified topic. If the local rights of the topic are enabled, then from the
   * inherited rights, only the admin ones are kept.
   *
   * @param topic the topic to update with the updated data
   * @param alertType the type of notification: notify all the users ("All"), only the publishers
   * ("publisher"), or nobody ("None").
   */
  public synchronized void updateTopic(NodeDetail topic, String alertType) {
    NodeDetail previousTopic = getNodeHeader(topic.getId());
    updateTopicHeader(topic, alertType);
    if (!previousTopic.haveLocalRights() && topic.haveLocalRights()) {
      initTopicSpecificAdminProfiles(topic, topic.getFatherPK());
    }
  }

  /**
   * Update only header information about the specified topic.
   *
   * @param nd the topic to update with the updated data
   * @param alertType the type of notification: notify all the users ("All"), only the publishers
   * ("publisher"), or nobody ("None").
   */
  public synchronized void updateTopicHeader(NodeDetail nd, String alertType) {
    nd.getNodePK().setSpace(getSpaceId());
    nd.getNodePK().setComponentName(getComponentId());
    if (isTopicAdmin(nd.getNodePK().getId())) {
      nd.setCreatorId(getUserId());
      nd.setCreationDate(new Date());
      final NodeDetail updatedNode = getKmeliaService().updateTopic(nd, alertType);
      if (updatedNode.getId().equals(getCurrentFolderId())) {
        processBreadcrumb(getCurrentFolderId());
      }
    }
    SilverLogger.getLogger(this).warn("Security alert from {0}", getUserId());
  }

  public synchronized NodeDetail getSubTopicDetail(String subTopicId) {
    return getKmeliaService().getSubTopicDetail(getNodePK(subTopicId));
  }

  public synchronized NodePK addSubTopic(NodeDetail nd, String alertType, String parentId) {
    nd.getNodePK().setSpace(getSpaceId());
    nd.getNodePK().setComponentName(getComponentId());
    nd.setCreatorId(getUserId());
    NodePK parentPk = getNodePK(parentId);
    NodeDetail node = getKmeliaService().addSubTopic(parentPk, nd, alertType);
    initTopicSpecificAdminProfiles(node, parentPk);

    return node.getNodePK();
  }

  private void initTopicSpecificAdminProfiles(NodeDetail node, NodePK parentPk) {
    // by default, sets father's admin rights in the case of specific rights
    // to prevent no access to users having some rights with the father topic
    String parentId = parentPk.getId();
    if (node.haveLocalRights()) {
      ProfileInst parentProfile;
      String profileAdmin = SilverpeasRole.ADMIN.getName();
      if (NodePK.ROOT_NODE_ID.equals(parentId)) {
        parentProfile = getProfile(profileAdmin);
      } else {
        parentProfile = getRecursiveTopicProfile(profileAdmin, parentId);
      }
      if (parentProfile != null) {
        updateTopicRole(profileAdmin, node.getId(),
            parentProfile.getAllGroups().toArray(new String[0]),
            parentProfile.getAllUsers().toArray(new String[0]));
      }
    }
  }

  public synchronized String deleteTopic(String topicId) {
    if (NodePK.ROOT_NODE_ID.equals(topicId) || NodePK.BIN_NODE_ID.equals(topicId)) {
      return null;
    }
    NodeDetail node = getNodeHeader(topicId);
    // check if user is allowed to delete this topic
    if (SilverpeasRole.ADMIN.isInRole(getUserTopicProfile(topicId)) ||
        SilverpeasRole.ADMIN.isInRole(getUserTopicProfile(NodePK.ROOT_NODE_ID)) ||
        SilverpeasRole.ADMIN.isInRole(getUserTopicProfile(node.getFatherPK().getId()))) {
      // First, remove rights on topic and its descendants
      List<NodeDetail> treeview = getNodeService().getSubTree(getNodePK(topicId));
      for (NodeDetail nodeToDelete : treeview) {
        deleteTopicRoles(nodeToDelete);
      }
      // Then, remove the topic itself
      getKmeliaService().deleteTopic(getNodePK(topicId));

      return node.getFatherPK().getId();
    }
    return null;
  }

  public synchronized void changeTopicStatus(String newStatus, String topicId,
      boolean recursiveChanges) {
    getKmeliaService().changeTopicStatus(newStatus, getNodePK(topicId), recursiveChanges);
  }

  public synchronized PublicationDetail getPublicationDetail(String pubId) {
    return getKmeliaService().getPublicationDetail(getPublicationPK(pubId));
  }

  private Collection<Collection<NodeDetail>> getPathList(PublicationPK pk) {
    return getKmeliaService().getPathList(pk);
  }

  public NodePK getBestAllowedPublicationFather(String pubId) {
    return getKmeliaService().getBestLocationOfPublicationForUser(getPublicationPK(pubId),
        getUserId());
  }

  public synchronized String createPublication(PublicationDetail pubDetail,
      final PdcClassificationEntity classification) {
    pubDetail.setCreatorId(getUserId());
    pubDetail.setCreationDate(new Date());

    //setCurrentLanguage(pubDetail.getLanguage());

    String result;
    if (isKmaxMode) {
      result = getKmeliaService().createKmaxPublication(pubDetail);
    } else {
      if (classification == null || classification.isUndefined()) {
        result = getKmeliaService().createPublicationIntoTopic(pubDetail, getCurrentFolderPK());
      } else {
        PdcClassification withClassification = getPdcClassification(pubDetail, classification);
        result = getKmeliaService()
            .createPublicationIntoTopic(pubDetail, getCurrentFolderPK(), withClassification);
      }
    }
    return result;
  }

  /**
   * attach uploaded files to the specified publication
   *
   * @param uploadedFiles list of uploaded files
   * @param pubDetail publication on which you want to attach files
   */
  public synchronized void addUploadedFilesToPublication(Collection<UploadedFile> uploadedFiles,
      PublicationDetail pubDetail) {
    Attachments.from(uploadedFiles).attachTo(LocalizedContribution.from(pubDetail,
        pubDetail.getLanguage()));
  }

  public synchronized void updatePublication(final PublicationDetail pubDetail) {
    updatePublication(pubDetail, null);
  }

  public synchronized void updatePublication(final PublicationDetail pubDetail,
      final PdcClassificationEntity classification) {
    pubDetail.getPK().setSpace(getSpaceId());
    pubDetail.getPK().setComponentName(getComponentId());
    pubDetail.setUpdaterId(getUserId());

    setCurrentLanguage(pubDetail.getLanguage());

    if (isCloneNeeded()) {
      clonePublication(pubDetail);
    } else {
      if (NodePK.BIN_NODE_ID.equals(getCurrentFolderId())) {
        // publication is in the trash can
        pubDetail.setIndexOperation(IndexManager.NONE);
      }

      if (getSessionClone() != null && getSessionClone().getId().equals(pubDetail.getId())) {
        // update the clone, clone stay in same status
        pubDetail.setStatusMustBeChecked(false);

        // clone must not be indexed
        pubDetail.setIndexOperation(IndexManager.NONE);
      }
      if (classification == null) {
        getKmeliaService().updatePublication(pubDetail);
      } else {
        PdcClassification withClassification = getPdcClassification(pubDetail, classification);
        getKmeliaService().updatePublication(pubDetail, withClassification);
      }
    }
  }

  private static PdcClassification getPdcClassification(final PublicationDetail pubDetail,
      final PdcClassificationEntity classification) {
    final List<PdcPosition> pdcPositions = classification.getPdcPositions();
    return aPdcClassificationOfContent(pubDetail).withPositions(pdcPositions);
  }

  public boolean isCloneNeeded() {
    if (getSessionPublication() == null) {
      return false;
    }
    String currentStatus = getSessionPublication().getDetail().getStatus();
    return (isPublicationAlwaysVisibleEnabled() && "writer".equals(getUserTopicProfile()) &&
        (getSessionClone() == null) && PublicationDetail.VALID_STATUS.equals(currentStatus));
  }

  public boolean isCloneNeededWithDraft() {
    return (isPublicationAlwaysVisibleEnabled() && (getSessionClone() == null));
  }

  public void clonePublication() {
    clonePublication(null);
  }

  public void clonePublication(PublicationDetail pubDetail) {
    String cloneStatus = PublicationDetail.TO_VALIDATE_STATUS;
    if (isDraftEnabled()) {
      cloneStatus = PublicationDetail.DRAFT_STATUS;
    }
    clonePublication(pubDetail, cloneStatus);
  }

  /**
   * Clone current publication. Create new publication based on pubDetail object if not null or
   * sessionPublication otherwise. Original publication must not be modified (except references to
   * clone : cloneId and cloneStatus).
   *
   * @param pubDetail If not null, attribute values are set to the clone
   * @param nextStatus Draft or ToValidate
   */
  private void clonePublication(PublicationDetail pubDetail, String nextStatus) {
    String cloneId;
    // récupération de la publi de référence
    CompletePublication refPubComplete = getSessionPublication().getCompleteDetail();
    try {
      cloneId = getKmeliaService().clonePublication(refPubComplete, pubDetail, nextStatus);
      setSessionClone(getPublication(cloneId));
    } catch (Exception e) {
      throw new KmeliaRuntimeException(e);
    }
  }

  public synchronized void deletePublication(String pubId) {
    getKmeliaService()
        .deletePublications(Collections.singletonList(pubId), getCurrentFolderPK(), getUserId());
  }

  public List<String> deleteSelectedPublications() {
    List<String> removed = getKmeliaService()
        .deletePublications(getLocalSelectedPublicationIds(), getCurrentFolderPK(), getUserId());
    resetSelectedPublicationPKs();
    return removed;
  }

  private List<String> getLocalSelectedPublicationIds() {
    List<String> ids = new ArrayList<>();
    for (PublicationPK pubPK : getSelectedPublicationPKs()) {
      if (pubPK != null && pubPK.getInstanceId().equals(getComponentId())) {
        ids.add(pubPK.getId());
      }
    }
    return ids;
  }

  public synchronized void deleteClone() {
    if (getSessionClone() != null) {
      // delete clone
      getKmeliaService().deleteClone(getSessionClone().getPk());

      setSessionClone(null);
      refreshSessionPubliAndClone();
    }
  }

  private static boolean isInteger(String id) {
    return StringUtil.isInteger(id);
  }

  private void refreshSessionPubliAndClone() {
    final KmeliaPublication sessionPubClone = getSessionClone();
    // Master refresh
    final String pubId = getSessionPublication().getDetail().getPK().getId();
    setSessionPublication(getPublication(pubId));
    if (sessionPubClone != null) {
      // Clone refresh
      final String cloneId = sessionPubClone.getDetail().getPK().getId();
      setSessionClone(getPublication(cloneId));
    }
  }

  /**
   * adds links between specified publication and other publications contained in links parameter
   *
   * @param pubId publication which you want removes the external link
   * @param links list of links to remove
   */
  private void addInfoLinks(String pubId, List<ResourceReference> links) {
    PublicationPK pubPK = getPublicationPK(pubId);
    getKmeliaService().addInfoLinks(pubPK, links);

    // reset current publication and its location
    NodePK nodePK = getKmeliaService().getPublicationFatherPK(pubPK);
    setCurrentFolderId(nodePK.getId(), true);

    KmeliaPublication completPub =
        getKmeliaService().getPublication(pubPK, getCurrentFolderPK());
    setSessionPublication(completPub);
  }

  public synchronized KmeliaPublication getPublication(String pubId) {
    return getPublication(pubId, false);
  }

  public synchronized KmeliaPublication getPublication(String pubId, boolean processIndex) {
    PublicationPK pubPK = getPublicationPK(pubId);
    // get publication
    KmeliaPublication publication = getKmeliaService().getPublication(pubPK, getCurrentFolderPK());
    PublicationDetail publicationDetail = publication.getDetail();

    ResourceReference resourceReference = new ResourceReference(pubId, getComponentId());
    if (!publicationDetail.getPK().getInstanceId().equals(getComponentId())) {
      // it's an alias
      resourceReference.setComponentName(publicationDetail.getPK().getInstanceId());
    }

    if (getSessionPublication() != null) {
      if (!pubId.equals(getSessionPublication().getId())) {
        // memorize the reading of the publication by the user
        getStatisticService().addStat(getUserId(), resourceReference, 1, PUBLICATION);
      }
    } else {
      getStatisticService().addStat(getUserId(), resourceReference, 1, PUBLICATION);
    }

    if (processIndex) {
      // getting rank of publication
      KmeliaPublication pub =
          KmeliaPublication.fromDetail(publicationDetail, getCurrentFolderPK());
      if (getSessionPublicationsList() != null) {
        rang = getSessionPublicationsList().indexOf(pub);
        if (rang != -1 && getSearchContext() != null) {
          getSessionPublicationsList().get(rang).setAsRead();
        }
      }
    }

    return publication;
  }

  public synchronized void orderPubs() {
    orderPubs(getSortValue());
  }

  private void applyVisibilityFilter() {
    final List<KmeliaPublication> publications = getSessionPublicationsList();
    setSessionPublicationsList(getKmeliaService()
        .filterPublications(publications, getComponentId(), SilverpeasRole.fromString(getProfile()),
            getUserId()));
  }

  private synchronized void orderPubs(int sortType) {
    cacheDirectlyPublicationsListInSession(sort(getSessionPublicationsList(), sortType));
  }

  private List<KmeliaPublication> sort(Collection<KmeliaPublication> publications, int sortType) {
    if (publications == null) {
      return Collections.emptyList();
    }
    List<KmeliaPublication> publicationsToSort = new ArrayList<>(publications);

    int sort = sortType;
    boolean manualSortingUsed = isManualSortingUsed(publicationsToSort);

    if (manualSortingUsed && !getSort().isExplicitSort()) {
      // publications are sorted manually and another sort is not explicitly chosen by the user
      // so publications are displayed according to manual order defined by admin
      sort = KmeliaPublicationSort.SORT_MANUAL;
    } else if (getSortValue() == KmeliaPublicationSort.SORT_MANUAL) {
      // Current sort is manual but publications are not sorted manually
      // so publications are displayed according to default sort defined on application level
      // or instance level
      sort = getDefaultSortValue();
    }

    // store current sort
    this.sortValue.setCurrentSort(sort);
    this.sortValue.setManualSortEnable(manualSortingUsed);
    this.sortValue.setExplicitSort(false);
    this.sortValue.withContentLanguage(getCurrentLanguage()).sort(publicationsToSort);
    return publicationsToSort;
  }

  private boolean isManualSortingUsed(List<KmeliaPublication> publications) {
    for (KmeliaPublication publication : publications) {
      if (publication.getDetail().getExplicitRank() > 0) {
        return true;
      }
    }
    return false;
  }

  public void orderPublications(List<String> sortedPubIds) {
    getPublicationService().changePublicationsOrder(sortedPubIds, getCurrentFolderPK());
  }

  public void resetPublicationsOrder() {
    // reset order in database
    getPublicationService().resetPublicationsOrder(getCurrentFolderPK());
    // reload session cache
    loadPublicationsOfCurrentFolder();
    // order publications according to default sort
    this.sortValue.setCurrentSort(getDefaultSortValue());
    orderPubs();
  }

  public Collection<PublicationDetail> getAllPublications() {
    return getAllPublications(null);
  }

  /**
   * Get all publications sorted
   *
   * @param sortedBy (example: pubName asc)
   * @return Collection of Publications
   */
  public Collection<PublicationDetail> getAllPublications(String sortedBy) {
    String publicationDefaultSorting =
        getSettings().getString("publication_defaultsorting", "pubId desc");
    if (StringUtil.isDefined(sortedBy)) {
      publicationDefaultSorting = sortedBy;
    }
    return getPublicationService().getAllPublications(getComponentId(), publicationDefaultSorting);
  }

  public Collection<PublicationDetail> getAllPublicationsByTopic(String instanceId,
      List<String> fatherIds) {
    return getPublicationService().
        getDetailsByFatherIdsAndStatus(fatherIds, instanceId, "P.pubUpdateDate desc, P.pubId desc",
            PublicationDetail.VALID_STATUS);
  }

  /**
   * Get all visible publications
   *
   * @return List of WAAtributeValuePair (Id and InstanceId)
   */
  public List<WAAttributeValuePair> getAllVisiblePublications() {
    List<WAAttributeValuePair> allVisiblesPublications = new ArrayList<>();
    Collection<PublicationDetail> allPublications = getAllPublications();

    for (PublicationDetail pubDetail : allPublications) {
      if (pubDetail.getStatus().equals(PublicationDetail.VALID_STATUS)) {
        allVisiblesPublications.add(new WAAttributeValuePair(pubDetail.getId(), pubDetail.
            getInstanceId()));
      }
    }
    return allVisiblesPublications;
  }

  public List<WAAttributeValuePair> getAllVisiblePublicationsByTopic(String topicId) {
    List<WAAttributeValuePair> allVisiblesPublications = new ArrayList<>();
    // récupérer la liste des sous thèmes de topicId
    List<String> fatherIds = new ArrayList<>();
    NodePK nodePK = new NodePK(topicId, getComponentId());
    List<NodeDetail> nodes = getNodeService().getSubTree(nodePK);
    for (NodeDetail node : nodes) {
      fatherIds.add(node.getId());
    }
    // création de pubPK
    Collection<PublicationDetail> allPublications =
        getAllPublicationsByTopic(getComponentId(), fatherIds);

    for (PublicationDetail pubDetail : allPublications) {
      if (pubDetail.getStatus().equals(PublicationDetail.VALID_STATUS)) {
        allVisiblesPublications.add(new WAAttributeValuePair(pubDetail.getId(), pubDetail.
            getInstanceId()));
      }
    }
    return allVisiblesPublications;
  }

  public int getIndexOfFirstPubToDisplay() {
    if (getSearchContext() != null) {
      return getSearchContext().getPaginationIndex();
    }
    return indexOfFirstPubToDisplay;
  }

  public void setIndexOfFirstPubToDisplay(int index) {
    this.indexOfFirstPubToDisplay = index;
  }

  public void processTopicWysiwyg(String topicId) {
    getNodeService().processWysiwyg(getNodePK(topicId));
  }

  /**
   * Si le mode brouillon est activé et que le classement PDC est possible alors une publication ne
   * peut sortir du mode brouillon que si elle est classée sur le PDC
   *
   * @return true si le PDC n'est pas utilisé ou si aucun axe n'est utilisé par le composant ou si
   * la publication est classée sur le PDC
   */
  public boolean isPublicationTaxonomyOK() {
    if (!isPdcUsed() || getSessionPublication() == null || !isPDCClassifyingMandatory()) {
      // Classification is not used or mandatory so we don't care about the current
      // classification of the content
      return true;
    }
    String pubId = getSessionPublication().getDetail().getPK().getId();
    return isPublicationClassifiedOnPDC(pubId);
  }

  public boolean isPublicationValidatorsOK() {
    if (getSessionPubliOrClone() != null && SilverpeasRole.WRITER.isInRole(getUserTopicProfile()) &&
        (isTargetValidationEnable() || isTargetMultiValidationEnable())) {
      return CollectionUtil
          .isNotEmpty(getKmeliaService().getActiveValidatorIds(getSessionPubliOrClone().getPk()));
    }
    return true;
  }

  /**
   * Gets the authorized publications from given references into a context of read.
   *
   * @param references the list of {@link ResourceReference} instance.
   * @return a collection of {@link KmeliaPublication} instance.
   */
  public synchronized <T extends ResourceReference> Collection<KmeliaPublication> getPublications(
      List<T> references) {
    return getKmeliaService().getPublications(references, getUserId(), getCurrentFolderPK(), true);
  }

  /**
   * Gets the authorized publications from given references into context of modification.
   * <p>
   * Clones are handled.
   * </p>
   *
   * @param references the list of {@link ResourceReference} instance.
   * @return a collection of pair of {@link KmeliaPublication} instances. On left the publication,
   * on right the optional clone.
   */
  public synchronized <T extends ResourceReference> Collection<Pair<KmeliaPublication,
      KmeliaPublication>> getPublicationsForModification(
      final List<T> references) {
    return getKmeliaService().getPublicationsForModification(references, getUserId());
  }

  public synchronized boolean validatePublication(String publicationId) {
    boolean validationComplete = getKmeliaService()
        .validatePublication(getPublicationPK(publicationId), getUserId(), false, false);
    if (validationComplete) {
      setSessionClone(null);
      refreshSessionPubliAndClone();
    }
    return validationComplete;
  }

  public synchronized void forcePublicationValidation(String publicationId) {
    getKmeliaService()
        .validatePublication(getPublicationPK(publicationId), getUserId(), true, false);
  }

  public synchronized void unvalidatePublication(String publicationId, String refusalMotive) {
    getKmeliaService().unvalidatePublication(getPublicationPK(publicationId), getUserId(),
        refusalMotive,
        getValidationType());
    refreshSessionPubliAndClone();
  }

  public synchronized void suspendPublication(String publicationId, String defermentMotive) {
    getKmeliaService().suspendPublication(getPublicationPK(publicationId), defermentMotive,
        getUserId());
  }

  public List<ValidationStep> getValidationSteps() {
    List<ValidationStep> steps =
        getPublicationService().getValidationSteps(getSessionPubliOrClone().getDetail().getPK());

    // Get users who have already validate this publication
    List<String> validators = new ArrayList<>();
    for (ValidationStep step : steps) {
      step.setUserFullName(getOrganisationController().getUserDetail(step.getUserId()).
          getDisplayedName());
      validators.add(step.getUserId());
    }

    List<String> allValidators =
        getKmeliaService().getAllValidators(getSessionPubliOrClone().getDetail().getPK());

    for (String allValidator : allValidators) {
      if (!validators.contains(allValidator)) {
        ValidationStep step = new ValidationStep();
        step.setUserFullName(
            getOrganisationController().getUserDetail(allValidator).getDisplayedName());
        steps.add(step);
      }
    }

    return steps;
  }

  public boolean isUserCanValidatePublication() {
    return getKmeliaService()
        .isUserCanValidatePublication(getSessionPubliOrClone().getDetail().getPK(), getUserId());
  }

  public ValidationStep getValidationStep() {
    if (getValidationType() == KmeliaHelper.VALIDATION_TARGET_N) {
      return getPublicationService()
          .getValidationStepByUser(getSessionPubliOrClone().getDetail().getPK(), getUserId());
    }

    return null;
  }

  public synchronized void draftOutPublication() {

    NodePK currentFolderPK = getCurrentFolderPK();
    if (isKmaxMode) {
      currentFolderPK = null;
    }
    getKmeliaService().draftOutPublication(getSessionPublication().getDetail().getPK(),
        currentFolderPK,
        getProfile());

    if (!KmeliaHelper.ROLE_WRITER.equals(getUserTopicProfile())) {
      // always reset clone
      setSessionClone(null);
    }
    refreshSessionPubliAndClone();
  }

  /**
   * Change publication status from any state to draft
   *
   * @since 3.0
   */
  public synchronized void draftInPublication() {
    if (isCloneNeededWithDraft()) {
      clonePublication();
    } else {
      getKmeliaService().draftInPublication(getSessionPubliOrClone().getDetail().getPK(),
          getUserId());
    }
    refreshSessionPubliAndClone();
  }

  public boolean isIndexable(PublicationDetail pubDetail) {
    return KmeliaHelper.isIndexable(pubDetail);
  }

  /**
   * adds links between specified publication and other publications contained in links parameter
   *
   * @param pubId publication which you want removes the external link
   * @param links list of links to remove
   * @return the number of links created
   */
  public int addPublicationsToLink(String pubId, Set<String> links) {
    List<ResourceReference> infoLinks = new ArrayList<>();
    for (String link : links) {
      StringTokenizer tokens = new StringTokenizer(link, "-");
      infoLinks.add(new ResourceReference(tokens.nextToken(), tokens.nextToken()));
    }
    addInfoLinks(pubId, infoLinks);
    return infoLinks.size();
  }

  /**
   * @param topicDetail the topic detail
   */
  public void setSessionTopic(TopicDetail topicDetail) {
    // Session objects
    if (topicDetail != null) {
      setCurrentFolderId(topicDetail.getNodePK().getId(), true);
      Collection<KmeliaPublication> publications = topicDetail.getKmeliaPublications();
      setSessionPublicationsList((List<KmeliaPublication>) publications);
    } else {
      setCurrentFolderId(NodePK.ROOT_NODE_ID, true);
    }
  }

  public void setSessionTopicToLink(TopicDetail topicDetail) {
    this.sessionTopicToLink = topicDetail;
  }

  public void setSessionPublication(KmeliaPublication pubDetail) {
    this.sessionPublication = pubDetail;
    setSessionClone(null);
  }

  public void setSessionClone(KmeliaPublication clone) {
    this.sessionClone = clone;
  }

  public void setSessionPath(String path) {
    this.sessionPath = path;
  }

  public void setSessionPathString(String path) {
    this.sessionPathString = path;
  }

  public void setSessionOwner(boolean owner) {
    this.sessionOwner = owner;
  }

  public void setSessionPublicationsList(List<KmeliaPublication> publications) {
    setSessionPublicationsList(publications, true);
  }

  private void setSessionPublicationsList(List<KmeliaPublication> publications, boolean sort) {
    cacheDirectlyPublicationsListInSession(publications == null
        ? null
        : new ArrayList<>(publications));
    if (sort) {
      orderPubs();
    }
  }

  private void cacheDirectlyPublicationsListInSession(final List<KmeliaPublication> publications) {
    getSessionCacheAccessor().getCache().put(getPublicationListSessionCacheKey(), publications);
  }

  public void setSessionCombination(List<String> combination) {
    this.sessionCombination = (combination == null ? null : new ArrayList<>(combination));
  }

  public void setSessionTimeCriteria(String timeCriteria) {
    this.sessionTimeCriteria = timeCriteria;
  }

  public int getSortValue() {
    return this.sortValue.getCurrentSort();
  }

  public void setSortValue(int sort) {
    this.sortValue.setCurrentSort(sort);
    this.sortValue.setExplicitSort(true);
    orderPubs();
  }

  public KmeliaPublicationSort getSort() {
    return this.sortValue;
  }

  public String getCurrentFolderId() {
    return currentFolderId;
  }

  public NodePK getCurrentFolderPK() {
    return new NodePK(getCurrentFolderId(), getComponentId());
  }

  public NodeDetail getCurrentFolder() {
    return getNodeHeader(getCurrentFolderId());
  }

  public void setCurrentFolderId(String id, boolean resetSessionPublication) {
    if (!id.equals(currentFolderId)) {
      indexOfFirstPubToDisplay = 0;
      resetSelectedPublicationPKs();
      setAllPublicationsListSelected(false);
      processBreadcrumb(id);
    }
    if (resetSessionPublication) {
      setSessionPublication(null);
    }
    currentFolderId = id;
  }

  public TopicDetail getSessionTopicToLink() {
    return this.sessionTopicToLink;
  }

  public KmeliaPublication getSessionPublication() {
    return this.sessionPublication;
  }

  public KmeliaPublication getSessionClone() {
    return this.sessionClone;
  }

  public KmeliaPublication getSessionPubliOrClone() {
    KmeliaPublication publication = getSessionClone();
    if (publication == null) {
      publication = getSessionPublication();
    }
    return publication;
  }

  public String getSessionPath() {
    return this.sessionPath;
  }

  public String getSessionPathString() {
    return this.sessionPathString;
  }

  public boolean getSessionOwner() {
    return this.sessionOwner;
  }

  @SuppressWarnings("unchecked")
  public List<KmeliaPublication> getSessionPublicationsList() {
    return (List<KmeliaPublication>) getSessionCacheAccessor().getCache().get(getPublicationListSessionCacheKey());
  }

  public List<String> getSessionCombination() {
    return this.sessionCombination;
  }

  public String getSessionTimeCriteria() {
    return this.sessionTimeCriteria;
  }

  private void removeSessionObjects() {
    setSessionTopic(null);
    setSessionTopicToLink(null);
    setSessionPublication(null);
    setSessionClone(null);
    setSessionPath(null);
    setSessionPathString(null);
    setSessionOwner(false);
    setSessionPublicationsList(null);
  }

  @SuppressWarnings("unchecked")
  public String initUPToSelectValidator(String formElementName, String formElementId,
      String folderId) {
    String mContext = URLUtil.getApplicationURL();
    Pair<String, String> hostComponentName = new Pair<>(getComponentLabel(), "");
    Pair<String, String>[] hostPath = new Pair[1];
    hostPath[0] = new Pair<>(getString("kmelia.SelectValidator"), "");
    String hostUrl = mContext + URLUtil.getURL(USELESS, getComponentId()) + "SetValidator";
    String cancelUrl = mContext + URLUtil.getURL(USELESS, getComponentId()) + "SetValidator";

    Selection sel = getSelection();
    sel.resetAll();
    sel.setHostSpaceName(getSpaceLabel());
    sel.setHostComponentName(hostComponentName);
    sel.setHostPath(hostPath);

    sel.setGoBackURL(hostUrl);
    sel.setCancelURL(cancelUrl);

    sel.setHtmlFormName("pubForm");
    if (!StringUtil.isDefined(formElementName)) {
      formElementName = "Valideur";
    }
    sel.setHtmlFormElementName(formElementName);
    if (!StringUtil.isDefined(formElementId)) {
      formElementId = "ValideurId";
    }
    sel.setHtmlFormElementId(formElementId);
    sel.setMultiSelect(isTargetMultiValidationEnable());
    sel.setPopupMode(true);
    sel.setSetSelectable(false);

    if (getSessionPubliOrClone() != null) {
      String[] userIds = getKmeliaService().getActiveValidatorIds(getSessionPubliOrClone().getPk())
          .toArray(new String[0]);
      sel.setSelectedElements(userIds);
    }

    // Add extra params
    SelectionUsersGroups sug = new SelectionUsersGroups();
    sug.setComponentId(getComponentId());

    List<String> profiles = new ArrayList<>();
    profiles.add(SilverpeasRole.PUBLISHER.toString());
    profiles.add(SilverpeasRole.ADMIN.toString());

    NodeDetail node = getNodeHeader(getCurrentFolderId());
    if (StringUtil.isDefined(folderId) && !getCurrentFolderId().equals(folderId)) {
      node = getNodeHeader(folderId);
    }
    boolean haveRights = isRightsOnTopicsEnabled() && node.haveRights();
    if (haveRights) {
      sug.setObjectId(ProfiledObjectType.NODE.getCode() + node.getRightsDependsOn());
    }
    sug.setProfileNames(profiles);

    sel.setExtraParams(sug);

    return Selection.getSelectionURL();
  }

  public boolean isVersionControlled() {
    if (isPublicationAlwaysVisibleEnabled()) {
      // This mode is not compatible, for now, with the possibility to manage versioned
      // attachments.
      return false;
    }
    return StringUtil.getBooleanValue(getComponentParameterValue(VERSION_MODE));
  }

  public boolean isWriterApproval() {
    return false;
  }

  public boolean isTargetValidationEnable() {
    return String.valueOf(KmeliaHelper.VALIDATION_TARGET_1)
        .equalsIgnoreCase(getComponentParameterValue(InstanceParameters.validation));
  }

  public boolean isTargetMultiValidationEnable() {
    return String.valueOf(KmeliaHelper.VALIDATION_TARGET_N)
        .equalsIgnoreCase(getComponentParameterValue(InstanceParameters.validation));
  }

  public boolean isCollegiateValidationEnable() {
    return String.valueOf(KmeliaHelper.VALIDATION_COLLEGIATE)
        .equalsIgnoreCase(getComponentParameterValue(InstanceParameters.validation));
  }

  public boolean isValidationTabVisible() {
    boolean tabVisible =
        PublicationDetail.TO_VALIDATE_STATUS.equalsIgnoreCase(getSessionPubliOrClone().getDetail().
            getStatus());

    return tabVisible && (getValidationType() == KmeliaHelper.VALIDATION_COLLEGIATE ||
        getValidationType() == KmeliaHelper.VALIDATION_TARGET_N);
  }

  public int getValidationType() {
    if (isTargetValidationEnable()) {
      return KmeliaHelper.VALIDATION_TARGET_1;
    }
    if (isTargetMultiValidationEnable()) {
      return KmeliaHelper.VALIDATION_TARGET_N;
    }
    if (isCollegiateValidationEnable()) {
      return KmeliaHelper.VALIDATION_COLLEGIATE;
    }
    return KmeliaHelper.VALIDATION_CLASSIC;
  }

  public boolean isCoWritingEnable() {
    return StringUtil.getBooleanValue(getComponentParameterValue(InstanceParameters.coWriting));
  }

  private boolean isPublicationClassifiedOnPDC(String pubId) {
    if (pubId != null && !pubId.isEmpty()) {
      try {
        int silverObjectId = getKmeliaService().getSilverObjectId(getPublicationPK(pubId));
        List<ClassifyPosition> positions =
            getPdcManager().getPositions(silverObjectId, getComponentId());
        return !positions.isEmpty();
      } catch (Exception e) {
        throw new KmeliaRuntimeException(e);
      }
    }
    return false;
  }

  public boolean isCurrentPublicationHaveContent() {
    return (StringUtil.isDefined(WysiwygController.load(getComponentId(), getSessionPublication().
        getId(), getCurrentLanguage())) ||
        !isInteger(getSessionPublication().getCompleteDetail().getPublicationDetail().getInfoId()));
  }

  public boolean isPDCClassifyingMandatory() {
    try {
      return getPdcManager().isClassifyingMandatory(getComponentId());
    } catch (Exception e) {
      throw new KmeliaRuntimeException(e);
    }
  }

  public PdcManager getPdcManager() {
    return PdcManager.get();
  }

  public NodeService getNodeService() {
    return NodeService.get();
  }

  public PublicationService getPublicationService() {
    return PublicationService.get();
  }

  public ImportReport importFile(File fileUploaded, String importMode,
      boolean draftMode, int versionType) throws ImportExportException {
    ImportReport importReport = null;
    FileImport fileImport = new FileImport(this, fileUploaded);
    boolean draft = draftMode;
    if (isDraftEnabled() && isPDCClassifyingMandatory()) {
      // classifying on PDC is mandatory, set publication in draft mode
      draft = true;
    }
    fileImport.setVersionType(versionType);
    if (UNITARY_IMPORT_MODE.equals(importMode)) {
      importReport = fileImport.importFile(draft);
    } else if (MASSIVE_IMPORT_MODE_ONE_PUBLICATION.equals(importMode) &&
        FileUtil.isArchive(fileUploaded.getName())) {
      importReport = fileImport.importFiles(draft);
    } else if (MASSIVE_IMPORT_MODE_MULTI_PUBLICATIONS.equals(importMode) &&
        FileUtil.isArchive(fileUploaded.getName())) {
      importReport = fileImport.importFilesMultiPubli(draft);
    }

    //print a log file of the report
    LocalizationBundle multilang = ResourceLocator.getLocalizationBundle(
        "org.silverpeas.importExportPeas.multilang.importExportPeasBundle");
    MultiSilverpeasBundle resource = new MultiSilverpeasBundle(multilang, "fr");
    fileImport.writeImportToLog(importReport, resource);

    return importReport;
  }

  private PublicationPK getPublicationPK(String id) {
    return new PublicationPK(id, getSpaceId(), getComponentId());
  }

  private NodePK getNodePK(String id) {
    return new NodePK(id, getSpaceId(), getComponentId());
  }

  public boolean isPublicationDeleted(PublicationPK pk) {
    boolean isPublicationDeleted = false;
    try {
      Collection<Collection<NodeDetail>> pathList = getPathList(pk);
      if (pathList.size() == 1) {
        for (Collection<NodeDetail> path : pathList) {
          for (NodeDetail nodeInPath : path) {
            if (nodeInPath.getNodePK().isTrash()) {
              isPublicationDeleted = true;
            }
          }
        }
      }
    } catch (Exception e) {
      throw new KmeliaRuntimeException(e);
    }
    return isPublicationDeleted;
  }

  public void setModelUsed(String[] models) {
    String objectId = getCurrentFolderId();
    getKmeliaService().setModelUsed(models, getComponentId(), objectId);
  }

  /**
   * Gets a list of models used as publication content for the current topic. First a global model
   * for the whole Kmelia instance is looked for. If no such model exists, and if the specified flag
   * is set at true, then the models defined for the current topic are looked for.
   *
   * @param specificToTopic a flag indicating if the models specifics to the current topic have to
   * be taken into account.
   * @return a list of the available models as publication content.
   */
  public List<String> getModelUsed(boolean specificToTopic) {
    List<String> models = new ArrayList<>();
    String formNameAppLevel = getXMLFormNameForPublications();
    if (StringUtil.isDefined(formNameAppLevel)) {
      models.add(formNameAppLevel);
    } else if (specificToTopic) {
      String objectId = getCurrentFolderId();
      models.addAll(getKmeliaService().getModelUsed(getComponentId(), objectId));
    }
    return models;
  }

  public boolean isTimeAxisUsed() {
    return StringUtil.getBooleanValue(getComponentParameterValue("timeAxisUsed"));
  }

  public boolean isFieldDescriptionVisible() {
    String paramValue = getComponentParameterValue("useDescription");
    return "1".equalsIgnoreCase(paramValue) || "2".equalsIgnoreCase(paramValue) ||
        "".equals(paramValue);
  }

  public boolean isFieldDescriptionMandatory() {
    return "2".equalsIgnoreCase(getComponentParameterValue("useDescription"));
  }

  public boolean isFieldKeywordsVisible() {
    String paramValue = getComponentParameterValue("useKeywords");
    return StringUtil.getBooleanValue(paramValue) || "".equals(paramValue);
  }

  public boolean isFieldImportanceVisible() {
    if (isKmaxMode) {
      return StringUtil.getBooleanValue(getComponentParameterValue("useImportance"));
    }
    return getSettings().getBoolean("showImportance", true);
  }

  public boolean isFieldVersionVisible() {
    if (isKmaxMode) {
      return StringUtil.getBooleanValue(getComponentParameterValue("useVersion"));
    }
    return getSettings().getBoolean("showPubVersion", true);
  }

  public List<Integer> getTimeAxisKeys() {
    if (this.timeAxis == null) {
      LocalizationBundle timeSettings =
          ResourceLocator.getLocalizationBundle("org.silverpeas.kmelia.multilang.timeAxisBundle",
              getLanguage());
      Enumeration<String> keys = timeSettings.getSpecificKeys();
      List<Integer> orderKeys = new ArrayList<>();
      while (keys.hasMoreElements()) {
        String keyStr = keys.nextElement();
        Integer key = Integer.parseInt(keyStr);
        orderKeys.add(key);
      }
      Collections.sort(orderKeys);
      this.timeAxis = orderKeys;
    }
    return this.timeAxis;
  }

  public synchronized List<NodeDetail> getAxis() {
    return getKmeliaService().getAxis(getComponentId());
  }

  public synchronized List<NodeDetail> getAxisHeaders() {
    return getKmeliaService().getAxisHeaders(getComponentId());
  }

  public synchronized void addAxis(NodeDetail axis) {
    getKmeliaService().addAxis(axis, getComponentId());
  }

  public synchronized NodeDetail getNodeHeader(String id) {
    return getKmeliaService().getNodeHeader(id, getComponentId());
  }

  public synchronized void updateAxis(NodeDetail axis) {
    getKmeliaService().updateAxis(axis, getComponentId());
  }

  public synchronized void deleteAxis(String axisId) {
    getKmeliaService().deleteAxis(axisId, getComponentId());
  }

  public synchronized List<KmeliaPublication> search(List<String> combination) {
    cacheDirectlyPublicationsListInSession(
        new ArrayList<>(getKmeliaService().search(combination, getComponentId())));
    applyVisibilityFilter();
    return getSessionPublicationsList();
  }

  public synchronized List<KmeliaPublication> search(List<String> combination, int nbDays) {
    cacheDirectlyPublicationsListInSession(
        new ArrayList<>(getKmeliaService().search(combination, nbDays, getComponentId())));
    applyVisibilityFilter();
    return getSessionPublicationsList();
  }

  public synchronized List<KmeliaPublication> getUnbalancedPublications() {
    return (List<KmeliaPublication>) getKmeliaService().getUnbalancedPublications(getComponentId());
  }

  public synchronized void addPosition(String fatherId, NodeDetail position) {
    getKmeliaService().addPosition(fatherId, position, getComponentId(), getUserId());
  }

  public synchronized void updatePosition(NodeDetail position) {
    getKmeliaService().updatePosition(position, getComponentId());
  }

  public synchronized void deletePosition(String positionId) {
    getKmeliaService().deletePosition(positionId, getComponentId());
  }

  /*
   * /* Kmax - Publications
   */

  public synchronized Collection<Coordinate> getPublicationCoordinates(String pubId) {
    return getKmeliaService().getPublicationCoordinates(pubId, getComponentId());
  }

  public synchronized void addPublicationToCombination(String pubId, List<String> combination) {
    getKmeliaService().addPublicationToCombination(pubId, combination, getComponentId());
  }

  public synchronized void deletePublicationFromCombination(String pubId, String combinationId) {
    getKmeliaService().deletePublicationFromCombination(pubId, combinationId, getComponentId());
  }

  /**
   * Get session publications
   *
   * @return List of WAAtributeValuePair (Id and InstanceId)
   */
  public List<WAAttributeValuePair> getCurrentPublicationsList() {
    List<WAAttributeValuePair> currentPublications = new ArrayList<>();
    Collection<KmeliaPublication> allPublications = getSessionPublicationsList();

    for (KmeliaPublication aPubli : allPublications) {
      PublicationDetail pubDetail = aPubli.getDetail();
      if (pubDetail.getStatus().equals(PublicationDetail.VALID_STATUS)) {
        currentPublications
            .add(new WAAttributeValuePair(pubDetail.getId(), pubDetail.getInstanceId()));
      }
    }
    return currentPublications;
  }

  public synchronized Collection<NodeDetail> getPath(String positionId) {
    return getKmeliaService().getPath(positionId, getComponentId());
  }

  public void setCurrentCombination(List<String> combination) {
    this.currentCombination = new ArrayList<>(combination);
  }

  public List<String> getCurrentCombination() {
    return currentCombination;
  }

  /**
   * Transform combination axis from String /0/1037,/0/1038 in ArrayList /0/1037 then /0/1038
   * etc...
   *
   * @param axisValuesStr textual representation of axis values
   * @return Collection of combination
   */
  private List<String> convertStringCombination2List(String axisValuesStr) {
    List<String> combination = new ArrayList<>();
    StringTokenizer st = new StringTokenizer(axisValuesStr, ",");
    while (st.hasMoreTokens()) {
      String axisValue = st.nextToken();
      combination.add(axisValue);
    }
    return combination;
  }

  /**
   * Get combination Axis (ie: /0/1037)
   *
   * @param axisValuesStr textual representation of axis values
   * @return Collection of combination
   */
  public List<String> getCombination(String axisValuesStr) {

    return convertStringCombination2List(axisValuesStr);
  }

  private String getNearPublication(int direction) {
    // rechercher le rang de la publication précédente
    int rangNext = rang + direction;

    KmeliaPublication pub = getSessionPublicationsList().get(rangNext);
    String pubId = pub.getDetail().getId();

    // on est sur la précédente, mettre à jour le rang avec la publication courante
    rang = rangNext;

    return pubId;
  }

  /**
   * getPrevious
   *
   * @return previous publication id
   */
  public String getPrevious() {
    return getNearPublication(-1);
  }

  /**
   * getNext
   *
   * @return next publication id
   */
  public String getNext() {
    return getNearPublication(1);
  }

  public int getRang() {
    return rang;
  }

  private synchronized boolean isDragAndDropEnableByUser() {
    return getPersonalization().isDragAndDropEnabled();
  }

  public boolean isDragAndDropEnable() {
    return isDragAndDropEnableByUser && isMassiveDragAndDropAllowed() &&
        !AttachmentHugeProcessManager.get().isOneRunningOnInstance(getComponentId());
  }

  public String getCurrentLanguage() {
    return currentLanguage;
  }

  public void setCurrentLanguage(String currentLanguage) {
    this.currentLanguage = currentLanguage;
    processBreadcrumb(getCurrentFolderId());
  }

  @SuppressWarnings("unchecked")
  public void initUserPanelForTopicProfile(String role, String nodeId, String[] groupIds,
      String[] userIds) {
    String context = URLUtil.getApplicationURL();
    Pair<String, String>[] hostPath = new Pair[1];
    hostPath[0] = new Pair<>(getString("kmelia.SelectValidator"), "");

    Selection sel = getSelection();
    sel.resetAll();
    sel.setHostSpaceName(getSpaceLabel());
    sel.setHostComponentName(new Pair<>(getComponentLabel(), ""));
    sel.setHostPath(hostPath);

    String hostUrl = context + URLUtil.getURL(USELESS, getComponentId()) +
        "TopicProfileSetUsersAndGroups?Role=" + role + "&NodeId=" + nodeId;
    String cancelUrl = context + URLUtil.getURL(USELESS, getComponentId()) + "CloseWindow";

    sel.setGoBackURL(hostUrl);
    sel.setCancelURL(cancelUrl);
    sel.setPopupMode(true);
    sel.setHtmlFormElementId("roleItems");
    sel.setHtmlFormName("dummy");

    List<ProfileInst> profiles =
        getAdmin().getProfilesByObject(ProfiledObjectId.fromNode(nodeId), getComponentId());
    ProfileInst topicProfile = getProfile(profiles, role);

    SelectionUsersGroups sug = new SelectionUsersGroups();
    sug.setComponentId(getComponentId());
    sel.setExtraParams(sug);

    if (topicProfile != null) {
      sel.setSelectedElements(userIds);
      sel.setSelectedSets(groupIds);
    }
  }

  private void deleteTopicRoles(NodeDetail node) {
    if (node != null && node.haveLocalRights()) {
      List<ProfileInst> profiles = getTopicProfiles(node.getNodePK().getId());
      if (profiles != null) {
        for (ProfileInst profile : profiles) {
          if (profile != null && StringUtil.isDefined(profile.getId())) {
            deleteTopicRole(profile.getId());
          }
        }
      }
    }
  }

  private void deleteTopicRole(String profileId) {
    // Remove the profile
    getAdmin().deleteProfileInst(profileId);
  }

  public void updateTopicRole(String role, String nodeId, String[] groupIds, String[] userIds) {
    ProfileInst profile = getTopicProfile(role, nodeId);

    // Update the topic
    NodeDetail topic = getNodeHeader(nodeId);
    topic.setRightsDependsOnMe();
    getNodeService().updateRightsDependency(topic);

    profile.setUsers(Arrays.asList(userIds));
    profile.setGroups(Arrays.asList(groupIds));

    if (StringUtil.isDefined(profile.getId())) {
      if (profile.isEmpty()) {
        deleteTopicRole(profile.getId());
      } else {
        // Update the profile
        getAdmin().updateProfileInst(profile);
      }
    } else {
      profile.setObjectId(new ProfiledObjectId(ProfiledObjectType.NODE, nodeId));
      profile.setComponentFatherId(getComponentLocalId(getComponentId()));
      // Create the profile
      getAdmin().addProfileInst(profile);
    }
  }

  public ProfileInst getTopicProfile(String role, String topicId) {
    List<ProfileInst> profiles =
        getAdmin().getProfilesByObject(ProfiledObjectId.fromNode(topicId), getComponentId());
    for (ProfileInst profile : profiles) {
      if (profile.getName().equals(role)) {
        return profile;
      }
    }

    ProfileInst profile = new ProfileInst();
    profile.setName(role);
    return profile;
  }

  public ProfileInst getProfile(String role) {
    ComponentInst componentInst = getAdmin().getComponentInst(getComponentId());
    ProfileInst profile = componentInst.getProfileInst(role);
    ProfileInst inheritedProfile = componentInst.getInheritedProfileInst(role);

    if (profile == null && inheritedProfile == null) {
      profile = new ProfileInst();
      profile.setName(role);

      return profile;
    } else if (profile != null && inheritedProfile == null) {
      return profile;
    } else if (profile == null) {
      return inheritedProfile;
    } else {
      // merge des profiles
      ProfileInst newProfile = new ProfileInst(profile);
      newProfile.addGroups(inheritedProfile.getAllGroups());
      newProfile.addUsers(inheritedProfile.getAllUsers());

      return newProfile;
    }
  }

  private ProfileInst getRecursiveTopicProfile(String role, String topicId) {
    String currentId = topicId;
    ProfileInst profile = getTopicProfile(role, currentId);
    while (profile.isEmpty()) {
      NodeDetail topic = getNodeHeader(currentId);
      currentId = topic.getFatherPK().getId();
      if (currentId.equals(NodePK.ROOT_NODE_ID)) {
        profile = getProfile(role);
      } else {
        profile = getTopicProfile(role, currentId);
      }
    }
    return profile;
  }

  public List<ProfileInst> getTopicProfiles() {
    return getTopicProfiles(getCurrentFolderId());
  }

  public List<ProfileInst> getTopicProfiles(String topicId) {
    List<ProfileInst> alShowProfile = new ArrayList<>();
    String[] asAvailProfileNames = getAdmin().getAllProfilesNames(KMELIA);
    for (String asAvailProfileName : asAvailProfileNames) {
      ProfileInst profile = getTopicProfile(asAvailProfileName, topicId);
      profile.setLabel(
          getAdmin().getProfileLabelByName(KMELIA, asAvailProfileName, getLanguage()));
      alShowProfile.add(profile);
    }

    return alShowProfile;

  }

  public List<Group> groupIds2Groups(List<String> groupIds) {
    List<Group> res = new ArrayList<>();
    for (int nI = 0; groupIds != null && nI < groupIds.size(); nI++) {
      Group theGroup = getAdmin().getGroupById(groupIds.get(nI));
      if (theGroup != null) {
        res.add(theGroup);
      }
    }

    return res;
  }

  public List<UserDetail> userIds2Users(List<String> userIds) {
    List<UserDetail> res = new ArrayList<>();
    if (userIds != null) {
      for (String userId : userIds) {
        UserDetail user = UserDetail.getById(userId);
        if (user != null) {
          res.add(user);
        }
      }
    }
    return res;
  }

  private AdminController getAdmin() {
    return ServiceProvider.getService(AdminController.class);
  }

  private ProfileInst getProfile(List<ProfileInst> profiles, String role) {
    for (ProfileInst profile : profiles) {
      if (role.equals(profile.getName())) {
        return profile;
      }
    }
    return null;
  }

  public void copyPublication(String pubId) throws ClipboardException {
    PublicationDetail pub = getPublicationDetail(pubId);
    // Can only copy user accessed publication
    PublicationAccessControl publicationAccessController = PublicationAccessControl.get();
    if (publicationAccessController.isUserAuthorized(getUserId(), pub.getPK())) {
      PublicationSelection pubSelect = new PublicationSelection(pub, getCurrentFolderPK());
      addClipboardSelection(pubSelect);
    } else {
      SilverLogger.getLogger(this)
          .warn("Security alert from user {0} trying to copy publication {1}", getUserId(), pubId);
      throw new ClipboardException("Security purpose, access to publication is forbidden");
    }
  }

  private void copyPublications(List<PublicationPK> pubPKs) throws ClipboardException {
    for (PublicationPK pubPK : pubPKs) {
      if (pubPK != null) {
        copyPublication(pubPK.getId());
      }
    }
  }

  public void copySelectedPublications() throws ClipboardException {
    copyPublications(getSelectedPublicationPKs());
  }

  public void cutPublication(String pubId) throws ClipboardException {
    PublicationDetail pub = getPublicationDetail(pubId);
    // Can only copy user accessed publication
    PublicationAccessControl publicationAccessController = PublicationAccessControl.get();
    if (publicationAccessController.isUserAuthorized(getUserId(), pub.getPK())) {
      PublicationSelection pubSelect = new PublicationSelection(pub, getCurrentFolderPK());
      pubSelect.setCutted(true);

      addClipboardSelection(pubSelect);
    } else {
      SilverLogger.getLogger(this)
          .warn("Security alert from user {0} trying to cut publication {1}", getUserId(), pubId);
      throw new ClipboardException("Security purpose, access to publication is forbidden");
    }
  }

  private void cutPublications(List<PublicationPK> pubPKs) throws ClipboardException {
    for (PublicationPK pubPK : pubPKs) {
      if (pubPK != null && pubPK.getInstanceId().equals(getComponentId())) {
        cutPublication(pubPK.getId());
      }
    }
  }

  public void cutSelectedPublications() throws ClipboardException {
    cutPublications(getSelectedPublicationPKs());
  }

  public void copyTopic(String id) throws ClipboardException {
    NodeDetail nodeDetail = getNodeHeader(id);
    if (nodeDetail.canBeModifiedBy(getUserDetail())) {
      NodeSelection nodeSelect = new NodeSelection(getNodeHeader(id));

      addClipboardSelection(nodeSelect);
    } else {
      SilverLogger.getLogger(this)
          .warn("Security alert from user {0} trying to copy topic {1}", getUserId(), id);
      throw new ClipboardException("Security purpose : access to node is forbidden");
    }
  }

  public void cutTopic(String id) throws ClipboardException {
    NodeDetail nodeDetail = getNodeHeader(id);
    if (nodeDetail.canBeModifiedBy(getUserDetail())) {
      NodeSelection nodeSelect = new NodeSelection(getNodeHeader(id));
      nodeSelect.setCutted(true);

      addClipboardSelection(nodeSelect);
    } else {
      SilverLogger.getLogger(this)
          .warn("Security alert from user {0} trying to cut topic {1}", getUserId(), id);
      throw new ClipboardException("Security purpose : access to node is forbidden");
    }
  }

  public void paste(KmeliaPasteDetail pasteDetail) throws ClipboardException {
    resetSelectedPublicationPKs();
    pasteDetail.setUserId(getUserId());
    try {
      final NodeDetail targetNode = getNodeHeader(pasteDetail.getToPK().getId());
      final Collection<ClipboardSelection> selectedObjects = getClipboardSelectedObjects();
      final List<PublicationPK> pubPkMoved = new ArrayList<>();
      final List<PublicationPK> publicationPkCopied = new ArrayList<>();
      final List<NodePK> nodePkMoved = new ArrayList<>();
      final List<NodePK> nodePkCopied = new ArrayList<>();
      for (ClipboardSelection selection : selectedObjects) {
        if (selection == null) {
          continue;
        }
        if (selection.isDataFlavorSupported(PublicationSelection.PublicationDetailFlavor) && isPublicationAllowed(targetNode.isRoot())) {
          PublicationSelection.TransferData data =
              (PublicationSelection.TransferData) selection.getTransferData(
                  PublicationSelection.PublicationDetailFlavor);
          final PublicationDetail pub = data.getPublicationDetail();
          Optional.of(pub)
              .filter(p -> selection.isCutted())
              .ifPresentOrElse(p -> pubPkMoved.add(pub.getPK()),
                  () -> publicationPkCopied.add(pub.getPK()));
        } else if (selection.isDataFlavorSupported(NodeSelection.NodeDetailFlavor)) {
          final NodeDetail node =
              (NodeDetail) selection.getTransferData(NodeSelection.NodeDetailFlavor);
          // check if current topic is a subTopic of node
          Optional.of(node)
              .filter(n -> !n.equals(targetNode) && !n.isFatherOf(targetNode))
              .ifPresent(n -> Optional.of(n)
                  .filter(nd -> selection.isCutted())
                  .ifPresentOrElse(nd -> nodePkMoved.add(node.getNodePK()),
                      () -> nodePkCopied.add(node.getNodePK())));
        }
      }
      SimulationActionProcessProcessor.get()
          .withContext(s -> s.getSourcePKs().addAll(pubPkMoved))
          .listElementsWith(KmeliaPublicationSimulationElementLister::new)
          .byAction(() -> ActionType.MOVE)
          .andWithContext(s -> s.getSourcePKs().addAll(publicationPkCopied))
          .listElementsWith(KmeliaPublicationSimulationElementLister::new)
          .byAction(() -> ActionType.COPY)
          .andWithContext(s -> s.getSourcePKs().addAll(nodePkMoved))
          .listElementsWith(KmeliaNodeSimulationElementLister::new)
          .byAction(() -> ActionType.MOVE)
          .andWithContext(s -> s.getSourcePKs().addAll(nodePkCopied))
          .listElementsWith(KmeliaNodeSimulationElementLister::new)
          .byAction(() -> ActionType.COPY)
          .toTargets(t -> t.getTargetPKs().add(targetNode.getNodePK()))
          .setLanguage(() -> currentLanguage)
          .execute(() -> {
            selectedObjects.stream()
                .filter(Objects::nonNull)
                .forEach(s -> {
                  try {
                    pasteClipboardSelection(s, pasteDetail, targetNode);
                  } catch (UnsupportedFlavorException e) {
                    throw new KmeliaRuntimeException(e);
                  }
                });
            return null;
          });

    } catch (ClipboardException | UnsupportedFlavorException e) {
      throw new KmeliaRuntimeException(e);
    }
    clipboardPasteDone();
  }

  /**
   * Is publications can be created in this node
   *
   * @param isRoot true or false
   * @return true or false
   */
  private boolean isPublicationAllowed(final boolean isRoot) {
    return (!isRoot) || (getNbPublicationsOnRoot() == 0);
  }

  private void pasteClipboardSelection(ClipboardSelection selection,
      KmeliaPasteDetail pasteDetail, final NodeDetail targetNode) throws UnsupportedFlavorException {
    if (selection.isDataFlavorSupported(PublicationSelection.PublicationDetailFlavor)) {
      PublicationSelection.TransferData data =
          (PublicationSelection.TransferData) selection.getTransferData(
              PublicationSelection.PublicationDetailFlavor);
      PublicationDetail pub = data.getPublicationDetail();
      if (selection.isCutted()) {
        pasteDetail.setFromPK(data.getFatherPK());
        movePublication(pub.getPK(), targetNode.getNodePK(), pasteDetail);
      } else {
        KmeliaCopyDetail copyDetail = KmeliaCopyDetail.fromPasteDetail(pasteDetail);
        copyDetail.setFromNodePK(data.getFatherPK());
        getKmeliaService().copyPublication(pub, copyDetail);
      }
    } else if (selection.isDataFlavorSupported(NodeSelection.NodeDetailFlavor)) {
      NodeDetail node = (NodeDetail) selection.getTransferData(NodeSelection.NodeDetailFlavor);
      // check if current topic is a subTopic of node
      boolean pasteAllowed = !node.equals(targetNode) && !node.isFatherOf(targetNode);
      if (pasteAllowed) {
        if (selection.isCutted()) {
          // move node
          getKmeliaService().moveNode(node.getNodePK(), targetNode.getNodePK(), pasteDetail);
        } else {
          // copy node
          KmeliaCopyDetail copyDetail = KmeliaCopyDetail.fromPasteDetail(pasteDetail);
          copyDetail.setFromNodePK(node.getNodePK());
          getKmeliaService().copyNode(copyDetail);
        }
      }
    }
  }

  public CLIPBOARD_STATE getClipboardState() {
    try {
      final Set<CLIPBOARD_STATE> states = getClipboardSelectedObjects().stream()
          .filter(Objects::nonNull)
          .filter(c -> c.isDataFlavorSupported(PublicationSelection.PublicationDetailFlavor)
              || c.isDataFlavorSupported(NodeSelection.NodeDetailFlavor))
          .map(c -> c.isCutted() ? HAS_CUTS : HAS_COPIES)
          .collect(toSet());
      final CLIPBOARD_STATE state;
      if (states.size() == 1) {
        state = states.iterator().next();
      } else if (states.size() > 1) {
        state = HAS_COPIES_AND_CUTS;
      } else {
        state = IS_EMPTY;
      }
      return state;
    } catch (ClipboardException e) {
      throw new KmeliaRuntimeException(e);
    }
  }

  private void movePublication(PublicationPK pubPK, NodePK nodePK, KmeliaPasteDetail pasteContext) {
    try {
      NodePK currentNodePK = nodePK;
      if (currentNodePK == null) {
        // Ajoute au thème courant
        currentNodePK = getCurrentFolderPK();
      }

      getKmeliaService().movePublication(pubPK, currentNodePK, pasteContext);
    } catch (Exception ex) {
      SilverLogger.getLogger(this).error(ex.getMessage(), ex);
    }
  }

  /**
   * get languages of publication header and attachments
   *
   * @return a List of String (language codes)
   */
  public List<String> getPublicationLanguages() {
    PublicationDetail pubDetail = getSessionPubliOrClone().getDetail();
    // get publication languages
    Collection<String> pubLanguages = pubDetail.getLanguages();
    List<String> languages = new ArrayList<>(pubLanguages);

    if (languages.size() == I18NHelper.getNumberOfLanguages()) {
      // Publication is translated in all supported languages
      return languages;
    } else {
      // get attachments languages
      List<String> attLanguages = getAttachmentLanguages();
      for (String language : attLanguages) {
        if (!languages.contains(language)) {
          languages.add(language);
        }
      }
    }

    return languages;
  }

  public List<String> getAttachmentLanguages() {
    PublicationPK pubPK = getSessionPubliOrClone().getDetail().getPK();
    // get attachments languages
    List<String> languages = new ArrayList<>();
    List<String> attLanguages = getLanguagesOfAttachments(new ResourceReference(pubPK.getId(),
        pubPK.
            getInstanceId()));
    for (String language : attLanguages) {
      if (!languages.contains(language)) {
        languages.add(language);
      }
    }
    return languages;
  }

  public Collection<Location> getPublicationLocations() {
    return getKmeliaService().getLocations(getSessionPublication().getDetail().getPK());
  }

  public List<NodeDetail> getAliasTreeview(String instanceId) {
    List<NodeDetail> tree = null;
    if (getKmeliaService().isUserCanPublish(instanceId, getUserId())) {
      NodePK root = new NodePK(NodePK.ROOT_NODE_ID, instanceId);
      tree = getKmeliaService().getTreeview(root, null, false, false, getUserId(), false,
          isRightsOnTopicsEnabled(instanceId));
    }
    return tree;
  }

  private boolean isToolbox() {
    return KmeliaHelper.isToolbox(getComponentId());
  }

  /**
   * Returns URL of single attached file for the current publication. If publication contains more
   * than one file, null is returned
   *
   * @param fromAlias true if getting document version from an alias.
   * @return URL of single attached file for the current publication. Null if publication contains
   * more than one file.
   */
  public String getSingleAttachmentURLOfCurrentPublication(final boolean fromAlias) {
    PublicationPK pubPK = getSessionPublication().getDetail().getPK();
    List<SimpleDocument> attachments = AttachmentServiceProvider.getAttachmentService().
        listDocumentsByForeignKey(pubPK.toResourceReference(), getLanguage());
    if (attachments.size() == 1) {
      SimpleDocument document = attachments.get(0);
      return getDocumentVersionURL(document, fromAlias);
    }
    return null;
  }

  /**
   * Return the url to access the file
   *
   * @param fileId the id of the file (attachment or document id).
   * @param fromAlias true if getting document version from an alias.
   * @return the url to the file.
   */
  public String getAttachmentURL(final String fileId, final boolean fromAlias) {
    SimpleDocument attachment = AttachmentServiceProvider.getAttachmentService().
        searchDocumentById(new SimpleDocumentPK(fileId), getLanguage());
    return getDocumentVersionURL(attachment, fromAlias);
  }

  /**
   * Returns URL of the right version of the given document according to current folder rights if
   * user is a reader, returns last public version (null if it does not exist) if user is not a
   * reader, returns last version (public or working one)
   *
   * @param document a loaded {@link SimpleDocument} instance.
   * @param fromAlias true if getting document version from an alias.
   * @return the URL of right version or null
   */
  private String getDocumentVersionURL(final SimpleDocument document, final boolean fromAlias) {
    return ofNullable(document.getLastPublicVersion())
        .map(v -> {
          if (!fromAlias && v.getVersionMaster().canBeAccessedBy(getUserDetail())) {
            return v.getVersionMaster();
          }
          return v;
        })
        .map(v -> URLUtil.getApplicationURL() + v.getAttachmentURL())
        .orElse(null);
  }

  public String getXmlFormForFiles() {
    return getComponentParameterValue("XmlFormForFiles");
  }

  public File exportPublication() {
    if (!isFormatSupported("zip")) {
      throw new KmeliaRuntimeException("Export format not yet supported");
    }
    PublicationPK pubPK = getSessionPublication().getDetail().getPK();
    File pdf = null;
    try {
      // create subdir to zip
      String subdir = "ExportPubli_" + pubPK.getId() + "_" + System.currentTimeMillis();
      String fileName = getPublicationExportFileName(getSessionPublication(), getLanguage());
      String subDirPath =
          FileRepositoryManager.getTemporaryPath() + subdir + File.separator + fileName;
      FileFolderManager.createFolder(subDirPath);
      // generate from the publication a document in PDF
      if (isFormatSupported(DocumentFormat.pdf.name())) {
        pdf = generateDocument(DocumentFormat.pdf, pubPK.getId());
        // copy pdf into zip
        FileRepositoryManager.copyFile(pdf.getPath(), subDirPath + File.separator + pdf.getName());
      }
      // copy files
      new AttachmentImportExport(getUserDetail()).getAttachments(pubPK.toResourceReference(),
          subDirPath, USELESS, null);

      String zipFileName = FileRepositoryManager.getTemporaryPath() + fileName + ".zip";
      // zip PDF and files
      ZipUtil.compressPathToZip(subDirPath, zipFileName);

      return new File(zipFileName);
    } catch (Exception e) {
      throw new KmeliaRuntimeException(e);
    } finally {
      if (pdf != null) {
        try {
          Files.delete(pdf.toPath());
        } catch (IOException e) {
          SilverLogger.getLogger(this).warn(e);
        }
      }
    }
  }

  public boolean isNotificationAllowed() {
    String parameterValue = getComponentParameterValue("notifications");
    if (!StringUtil.isDefined(parameterValue)) {
      return true;
    }
    return StringUtil.getBooleanValue(parameterValue);
  }

  public boolean isWysiwygOnTopicsEnabled() {
    return StringUtil.getBooleanValue(getComponentParameterValue("wysiwygOnTopics"));
  }

  public String getWysiwygOnTopic(String id) {
    String currentId = id;
    if (isWysiwygOnTopicsEnabled()) {
      if (!StringUtil.isDefined(currentId)) {
        currentId = getCurrentFolderId();
      }
      return WysiwygController.load(getComponentId(), "Node_" + currentId, getCurrentLanguage());
    }
    return "";
  }

  public String getWysiwygOnTopic() {
    return getWysiwygOnTopic(null);
  }

  public List<NodeDetail> getTopicPath(String topicId) {
    try {
      List<NodeDetail> pathInReverse = getNodeService().getPath(new NodePK(topicId,
          getComponentId()));
      Collections.reverse(pathInReverse);
      return pathInReverse;
    } catch (Exception e) {
      throw new KmeliaRuntimeException(e);
    }
  }

  public ThumbnailSettings getThumbnailSettings() {
    int width = getSettings().getInteger("vignetteWidth", -1);
    int height = getSettings().getInteger("vignetteHeight", -1);
    return ThumbnailSettings.getInstance(getComponentId(), width, height);
  }

  /**
   * return the value of component parameter "axisIdGlossary". This paramater indicate the axis of
   * pdc to use to highlight word in publication content
   *
   * @return an indentifier of Pdc axis
   */
  public String getAxisIdGlossary() {
    return getComponentParameterValue("axisIdGlossary");
  }

  public String getRole() {
    return getProfile();
  }

  public String displayPath(Collection<NodeDetail> path, boolean linked, int beforeAfter) {
    StringBuilder linkedPathString = new StringBuilder();
    StringBuilder pathString = new StringBuilder();
    int nbItemInPath = path.size();
    Iterator<NodeDetail> iterator = path.iterator();
    boolean alreadyCut = false;
    int i = 0;
    while (iterator.hasNext()) {
      NodeDetail nodeInPath = iterator.next();
      if ((i <= beforeAfter) || (i + beforeAfter >= nbItemInPath - 1)) {
        if (!nodeInPath.getNodePK().isRoot()) {
          setPathNode(linkedPathString, pathString, iterator, nodeInPath);
        }
      } else {
        if (!alreadyCut) {
          linkedPathString.append(" ... > ");
          pathString.append(" ... > ");
          alreadyCut = true;
        }
      }
      i++;
    }
    if (linked) {
      return linkedPathString.toString();
    } else {
      return pathString.toString();
    }
  }

  private void setPathNode(final StringBuilder linkedPathString, final StringBuilder pathString,
      final Iterator<NodeDetail> iterator, final NodeDetail nodeInPath) {
    String nodeName;
    if (nodeInPath.getNodePK().isTrash()) {
      nodeName = getString("kmelia.basket");
    } else {
      if (getCurrentLanguage() != null) {
        nodeName = nodeInPath.getName(getCurrentLanguage());
      } else {
        nodeName = nodeInPath.getName();
      }
    }
    linkedPathString.append("<a href=\"javascript:onClick=topicGoTo('")
        .append(nodeInPath.getNodePK().getId())
        .append("')\">")
        .append(Encode.forHtml(nodeName))
        .append("</a>");
    pathString.append(nodeName);
    if (iterator.hasNext()) {
      linkedPathString.append(" > ");
      pathString.append(" > ");
    }
  }

  /**
   * Is search in topics enabled
   *
   * @return boolean
   */
  public boolean isSearchOnTopicsEnabled() {
    return StringUtil.getBooleanValue(getComponentParameterValue("searchOnTopics").toLowerCase());
  }

  public boolean isAttachmentsEnabled() {
    return StringUtil.getBooleanValue(getComponentParameterValue("tabAttachments"));
  }

  /**
   * Get publications and aliases of this topic and its subtopics answering to the query
   *
   * @param queryDescription the query
   * @return List of Kmelia publications
   */
  public synchronized List<KmeliaPublication> search(QueryDescription queryDescription,
      PagesContext formContext) {

    String query = queryDescription.getQuery();

    // Insert this new search inside persistence layer in order to compute statistics
    final NodeDetail currentFolder = getCurrentFolder();
    final String nodePathSep = "/";
    final String safeCurrentFolderPath = getCurrentFolder().getFullPath().replaceFirst("/+$",
        "") + nodePathSep;
    TopicSearch newTS = new TopicSearch(getComponentId(), currentFolder.getId(),
        getUserId(), getLanguage(), query.toLowerCase(), new Date());
    KmeliaSearchServiceProvider.getTopicSearchService().createTopicSearch(newTS);

    List<KmeliaPublication> userPublications;
    queryDescription.setSearchingUser(getUserId());
    queryDescription.setRequestedFolder(getCurrentFolder().getFullPath());
    queryDescription.addComponent(getComponentId());
    queryDescription.setRequestedLanguage("*");

    try {
      List<MatchingIndexEntry> results = getSearchEngine().search(queryDescription).getEntries();
      results = results.stream()
          .filter(i -> PUBLICATION.equals(i.getObjectType()))
          .collect(Collectors.toList());
      final Map<PublicationPK, PublicationDetail> indexedUserPubs = new HashMap<>(results.size());
      final AtomicInteger rank = new AtomicInteger(0);
      getKmeliaService().getPublicationDetails(results.stream()
              .map(i -> new ResourceReference(i.getObjectId(), i.getComponent()))
              .collect(Collectors.toList()))
          .forEach(p -> indexedUserPubs.put(p.getPK(), p));
      userPublications = results.stream()
          .map(i -> {
            final PublicationPK pubPk = new PublicationPK(i.getObjectId(), i.getComponent());
            final PublicationDetail pub = indexedUserPubs.get(pubPk);
            if (pub == null) {
              return null;
            }
            final KmeliaPublication kPub = KmeliaPublication.fromDetail(pub);
            if (i.isAlias()
                || (i.getPaths() != null
                && !i.getPaths().isEmpty()
                && !(i.getPaths().get(0) + nodePathSep).startsWith(safeCurrentFolderPath))) {
              kPub.getDetail().setAlias(true);
            }
            kPub.getDetail().setExplicitRank(rank.getAndIncrement());
            return kPub;
          })
          .filter(Objects::nonNull)
          .collect(Collectors.toList());
    } catch (Exception pe) {
      throw new KmeliaRuntimeException(pe);
    }

    // store "in session" current search context
    formContext.setLanguage(getLanguage());
    SearchContext aSearchContext = new SearchContext(queryDescription, formContext);
    aSearchContext.setNode(currentFolder);
    aSearchContext.setResults(userPublications);
    setSearchContext(aSearchContext);

    // store results and keep search results order
    setSessionPublicationsList(userPublications, false);

    return userPublications;
  }

  /**
   * @return the list of SpaceInst from current space identifier (in session) to root space <br>
   * (all the subspace)
   */
  public List<SpaceInstLight> getSpacePath() {
    return this.getOrganisationController().getPathToSpace(this.getSpaceId());
  }

  /**
   * Gets an instance of PublicationTemplateManager.
   *
   * @return an instance of PublicationTemplateManager.
   */
  private PublicationTemplateManager getPublicationTemplateManager() {
    return PublicationTemplateManager.getInstance();
  }

  public List<PublicationTemplate> getForms() {
    List<PublicationTemplate> templates = new ArrayList<>();
    try {
      GlobalContext aContext = new GlobalContext(getSpaceId(), getComponentId());
      templates = getPublicationTemplateManager().getPublicationTemplates(aContext);
    } catch (PublicationTemplateException e) {
      SilverLogger.getLogger(this).error(e.getMessage(), e);
    }
    return templates;
  }

  @Override
  public String getPublicationExportFileName(KmeliaPublication publication, String language) {
    String lang = getLanguage();
    StringBuilder fileName = new StringBuilder(250);

    fileName.append(getUserDetail().getLogin()).append('-');

    // add space path to filename
    List<SpaceInstLight> listSpaces = getSpacePath();
    for (SpaceInstLight space : listSpaces) {
      fileName.append(space.getName(lang)).append('-');
    }
    // add component name to filename
    fileName.append(getComponentLabel());

    if (!isKmaxMode) {
      List<NodeDetail> path = getNodeService().getPath(getCurrentFolder().getNodePK());
      Collections.reverse(path);
      path.remove(0); // remove root folder
      for (NodeDetail node : path) {
        fileName.append('-').append(node.getName(lang));
      }
    }

    fileName.append('-').append(publication.getDetail().getTitle()).append('-');
    fileName.append(publication.getPk().getId());
    return StringUtil.toAcceptableFilename(fileName.toString());
  }

  public void removePublicationContent() {
    KmeliaPublication publication = getSessionPubliOrClone();
    PublicationPK pubPK = publication.getPk();
    getKmeliaService().removeContentOfPublication(pubPK);
    // reset reference to content
    publication.getDetail().setInfoId("0");
  }

  public boolean isComponentManageable() {
    return componentManageable;
  }

  /**
   * Gets all available export formats.
   *
   * @return a list of export formats Silverpeas supports for export.
   */
  public List<String> getAvailableFormats() {
    return Arrays.asList(AVAILABLE_EXPORT_FORMATS);
  }

  /**
   * Gets the export formats that are supported by the current Kmelia component instance. As some of
   * the export formats can be deactivated in the Kmelia settings file, this method returns all the
   * formats that are currently active.
   *
   * @return a list of export formats.
   */
  public List<String> getSupportedFormats() {
    String exportFormats = getSettings().getString(EXPORT_FORMATS, "");
    List<String> supportedFormats = new ArrayList<>();
    if (!exportFormats.trim().isEmpty()) {
      List<String> availableFormats = getAvailableFormats();
      for (String exportFormat : exportFormats.trim().split(" ")) {
        if (!availableFormats.contains(exportFormat)) {
          throw new KmeliaRuntimeException("Unknown format " + exportFormat);
        }
        supportedFormats.add(exportFormat);
      }
    }
    return supportedFormats;
  }

  /**
   * Is the specified export format is supported by the Kmelia component instance?
   *
   * @param format a recognized export format.
   * @return true if the specified format is currently supported for the publication export, false
   * otherwise.
   */
  public boolean isFormatSupported(String format) {
    return getSupportedFormats().contains(format);
  }

  /**
   * Is the default classification on the PdC used to classify the publications published in the
   * specified topic of the specified component instance can be modified during the
   * multi-publications import process? If no default classification is defined for the specified
   * topic (and for any of its parent topics), then false is returned.
   *
   * @param topicId the unique identifier of the topic.
   * @param componentId the unique identifier of the component instance.
   * @return true if the default classification can be modified during the automatical
   * classification of the imported publications. False otherwise.
   */
  public boolean isDefaultClassificationModifiable(String topicId, String componentId) {
    PdcClassificationService classificationService = PdcClassificationService.get();
    PdcClassification defaultClassification =
        classificationService.findAPreDefinedClassification(topicId, componentId);
    return defaultClassification != NONE_CLASSIFICATION && defaultClassification.isModifiable();
  }

  public void resetSelectedPublicationPKs() {
    this.selectedPublicationPKs.clear();
  }

  public List<PublicationPK> processSelectedPublicationIds(String selectedPublicationIds,
      String notSelectedPublicationIds) {
    StringTokenizer tokenizer;
    if (selectedPublicationIds != null) {
      tokenizer = new StringTokenizer(selectedPublicationIds, ",");
      while (tokenizer.hasMoreTokens()) {
        String[] str = StringUtil.splitByWholeSeparator(tokenizer.nextToken(), "-");
        PublicationPK pk = new PublicationPK(str[0], str[1]);
        PublicationAccessControl publicationAccessController = PublicationAccessControl.get();
        if (!this.selectedPublicationPKs.contains(pk) &&
            publicationAccessController.isUserAuthorized(getUserId(), pk,
                AccessControlContext.init())) {
          this.selectedPublicationPKs.add(pk);
        }
      }
    }

    if (notSelectedPublicationIds != null) {
      tokenizer = new StringTokenizer(notSelectedPublicationIds, ",");
      while (tokenizer.hasMoreTokens()) {
        String[] str = StringUtil.splitByWholeSeparator(tokenizer.nextToken(), "-");
        PublicationPK pk = new PublicationPK(str[0], str[1]);
        this.selectedPublicationPKs.remove(pk);
      }
    }

    return this.selectedPublicationPKs;
  }

  public List<PublicationPK> getSelectedPublicationPKs() {
    return selectedPublicationPKs;
  }

  public boolean isCustomPublicationTemplateUsed() {
    return customPublicationTemplateUsed;
  }

  public String getCustomPublicationTemplateName() {
    return customPublicationTemplateName;
  }

  public List<KmeliaPublication> getLatestPublications() {
    List<KmeliaPublication> publicationsToDisplay = new ArrayList<>();
    List<KmeliaPublication> toCheck = getKmeliaService()
        .getLatestAuthorizedPublications(getComponentId(), getUserId(), getNbPublicationsOnRoot());
    for (KmeliaPublication aPublication : toCheck) {
      if (!isPublicationDeleted(aPublication.getPk())) {
        publicationsToDisplay.add(aPublication);
      }
    }
    return publicationsToDisplay;
  }

  public void loadPublicationsOfCurrentFolder() {
    if (KmeliaHelper.isToValidateFolder(currentFolderId)) {
      setSessionPublicationsList(
          getKmeliaService().getPublicationsToValidate(getComponentId(), getUserId()));
    } else if (KmeliaHelper.isNonVisiblePubsFolder(currentFolderId)) {
      setSessionPublicationsList(
          getKmeliaService().getNonVisiblePublications(getComponentId(), getUserId()));
    } else {
      setSessionPublicationsList(getKmeliaService()
          .getAuthorizedPublicationsOfFolder(new NodePK(currentFolderId, getComponentId()),
              getUserTopicProfile(currentFolderId), getUserId(), isTreeStructure()));
    }
  }

  public String getContentLanguage() {
    if (getSessionPubliOrClone() != null) {
      if (getPublicationLanguages().contains(getCurrentLanguage())) {
        return getCurrentLanguage();
      }
      return I18NHelper.checkLanguage(getSessionPublication().getDetail().getLanguage());
    }
    return getCurrentLanguage();
  }

  public void setSearchContext(SearchContext searchContext) {
    this.searchContext = searchContext;
  }

  public SearchContext getSearchContext() {
    return searchContext;
  }

  public String manageSubscriptions() {
    final SubscriptionContext subscriptionContext = getSubscriptionContext();
    if (getSessionPublication() != null) {
      PublicationDetail publication = getSessionPublication().getDetail();
      List<NodeDetail> nodePath = getTopicPath(getCurrentFolderId());
      nodePath.remove(0);
      final SubscriptionResource resource;
      if (publication.isAlias()) {
        publication = publication.copy();
        final Location location = new Location(getCurrentFolderId(), getComponentId());
        location.setAsAlias(getUserId());
        publication.setAuthorizedLocation(location);
        resource =
            PublicationAliasSubscriptionResource.from(new PublicationPK(publication.getId(),
                getComponentId()));
      } else {
        resource = PublicationSubscriptionResource.from(publication);
      }
      subscriptionContext.initialize(resource)
          .atLocation(new Location(getCurrentFolderId(), getComponentId()))
          .withPath(Stream.concat(nodePath.stream()
                      .map(p -> new SubscriptionResourcePath(p.getName(getLanguage()),
                          p.getLink())),
                  Stream.of(new SubscriptionResourcePath(publication.getName(getLanguage()),
                      publication.getPermalink())))
              .collect(Collectors.toList()));
    } else if (getCurrentFolder().getNodePK().isRoot()) {
      subscriptionContext.initialize(ComponentSubscriptionResource.from(getComponentId()));
    } else {
      List<NodeDetail> nodePath = getTopicPath(getCurrentFolderId());
      nodePath.remove(0);
      subscriptionContext.initialize(NodeSubscriptionResource.from(getCurrentFolderPK()))
          .atLocation(new Location(getCurrentFolderId(), getComponentId()))
          .withNodePath(nodePath);
    }
    return subscriptionContext.getDestinationUrl();
  }

  public int getNbPublicationImported(ImportReport importReport) {
    int nbPublication = 0;
    List<ComponentReport> listComponentReport = importReport.getListComponentReport();
    for (ComponentReport componentRpt : listComponentReport) {
      nbPublication += componentRpt.getNbPublicationsCreated();
    }
    return nbPublication;
  }

  public String getErrorMessageImportation(ImportReport importReport, String importMode) {
    String message = null;
    LocalizationBundle attachmentResourceLocator =
        ResourceLocator.getLocalizationBundle("org.silverpeas.util.attachment.multilang.attachment",
            this.getLanguage());
    ComponentReport componentRpt = importReport.getListComponentReport().get(0);

    if (UNITARY_IMPORT_MODE.equals(importMode)) {
      //Unitary mode
      message = getReportOnOnePublicationImport(attachmentResourceLocator, componentRpt);
    } else if (MASSIVE_IMPORT_MODE_ONE_PUBLICATION.equals(importMode)) {
      //Massive mode, one publication
      message = getReportOnOnePublicationImport(attachmentResourceLocator, componentRpt);
    } else if (MASSIVE_IMPORT_MODE_MULTI_PUBLICATIONS.equals(importMode)) {
      //Massive mode, several publications
      message =
          getReportOnSeveralPublicationImport(attachmentResourceLocator, componentRpt);
    }
    return message;
  }

  private String getReportOnSeveralPublicationImport(
      final LocalizationBundle attachmentResourceLocator, final ComponentReport componentRpt) {
    String message = null;
    MassiveReport massiveReport = componentRpt.getListMassiveReports().get(0);
    for (UnitReport unitReport : massiveReport.getListUnitReports()) {
      if (unitReport.getError() == UnitReport.ERROR_FILE_SIZE_EXCEEDS_LIMIT) {
        message = getMaxSizeErrorMessage(attachmentResourceLocator);
      } else if (unitReport.getError() != UnitReport.ERROR_NO_ERROR) {
        message = attachmentResourceLocator.getString(INACCESSIBLE_ATTACHMENT);
        break;
      }
    }
    return message;
  }

  private String getReportOnOnePublicationImport(final LocalizationBundle attachmentResourceLocator,
      final ComponentReport componentRpt) {
    final String message;
    MassiveReport massiveReport = componentRpt.getListMassiveReports().get(0);
    UnitReport unitReport = massiveReport.getListUnitReports().get(0);
    if (unitReport.getError() == UnitReport.ERROR_NO_ERROR) {
      return null;
    } else if (unitReport.getError() == UnitReport.ERROR_FILE_SIZE_EXCEEDS_LIMIT) {
      message = getMaxSizeErrorMessage(attachmentResourceLocator);
    } else {
      message = attachmentResourceLocator.getString(INACCESSIBLE_ATTACHMENT);
    }
    return message;
  }

  public List<PublicationDetail> getListPublicationImported(ImportReport importReport,
      String importMode) {
    List<PublicationDetail> listPublicationDetail = new ArrayList<>();
    ComponentReport componentRpt = importReport.getListComponentReport().get(0);

    //Unitary mode
    if (UNITARY_IMPORT_MODE.equals(importMode)) {
      MassiveReport massiveReport = componentRpt.getListMassiveReports().get(0);
      UnitReport unitReport = massiveReport.getListUnitReports().get(0);
      if (unitReport.getStatus() == UnitReport.STATUS_PUBLICATION_CREATED) {
        String idPubli = unitReport.getLabel();
        PublicationDetail publicationDetail = this.getPublicationDetail(idPubli);
        listPublicationDetail.add(publicationDetail);
      }
    } //Massive mode, one publication
    else if (MASSIVE_IMPORT_MODE_ONE_PUBLICATION.equals(importMode)) {
      UnitReport unitReport = componentRpt.getListUnitReports().get(0);
      if (unitReport.getStatus() == UnitReport.STATUS_PUBLICATION_CREATED) {
        String idPubli = unitReport.getLabel();
        PublicationDetail publicationDetail = this.getPublicationDetail(idPubli);
        listPublicationDetail.add(publicationDetail);
      }

      //Massive mode, several publications
    } else if (MASSIVE_IMPORT_MODE_MULTI_PUBLICATIONS.equals(importMode)) {
      MassiveReport massiveReport = componentRpt.getListMassiveReports().get(0);
      for (UnitReport unitReport : massiveReport.getListUnitReports()) {
        if (unitReport.getStatus() == UnitReport.STATUS_PUBLICATION_CREATED) {
          String idPubli = unitReport.getLabel();
          PublicationDetail publicationDetail = this.getPublicationDetail(idPubli);
          listPublicationDetail.add(publicationDetail);
        }
      }
    }
    return listPublicationDetail;
  }

  private String getMaxSizeErrorMessage(LocalizationBundle messages) {
    long maximumFileSize = FileRepositoryManager.getUploadMaximumFileSize();
    String maximumFileSizeMo = UnitUtil.formatMemSize(maximumFileSize);
    return messages.getString("attachment.dialog.errorAtLeastOneFileSize") + " " + messages.
        getString("attachment.dialog.maximumFileSize") + " (" + maximumFileSizeMo + ")";
  }

  public List<HistoryObjectDetail> getLastAccess(PublicationPK pk) {
    return getKmeliaService().getLastAccess(pk, getCurrentFolderPK(), getUserId(), 4);
  }

  public void setPublicationValidator(String userIds) {
    getKmeliaService().setValidators(getSessionPubliOrClone().getDetail().getPK(), userIds);
    refreshSessionPubliAndClone();
  }

  /**
   * Check user access right on folder
   *
   * @param nodeId the topic/folder identifier to check
   * @return true if current user has admin access on topic given in parameter
   */
  public boolean isTopicAdmin(final String nodeId) {
    String profile = getUserTopicProfile(nodeId);
    if (profile != null) {
      return SilverpeasRole.getHighestFrom(SilverpeasRole.fromString(profile))
          .isGreaterThanOrEquals(SilverpeasRole.ADMIN);
    }
    return isUserComponentAdmin();
  }

  /*
   * Persist the date reminder for the given publication
   * @param pubDetail
   * @param dateReminderDate
   * @param messageReminder
   * @throws DateReminderException
   */
  private void createResourceDateReminder(final PublicationDetail pubDetail,
      final Date dateReminderDate, final String messageReminder) throws DateReminderException {

    if (dateReminderDate != null) {
      DateReminderDetail dateReminderDetail =
          new DateReminderDetail(dateReminderDate, messageReminder,
              DateReminderDetail.REMINDER_NOT_PROCESSED, pubDetail.getUpdaterId(),
              pubDetail.getUpdaterId());
      PublicationNoteReference publicationNoteReference =
          PublicationNoteReference.fromPublicationDetail(pubDetail);
      getDateReminderService().create(publicationNoteReference, dateReminderDetail);
    }
  }

  /**
   * Create the date reminder for the given publication
   *
   * @param pubDetail the publication
   * @param parameters date reminder parameters
   * @throws DateReminderException on error
   */
  public void addPublicationReminder(PublicationDetail pubDetail, List<FileItem> parameters)
      throws DateReminderException {
    String dateReminder = FileUploadUtil.getParameter(parameters, "DateReminder");
    String messageReminder = FileUploadUtil.getParameter(parameters, "MessageReminder");

    Date dateReminderDate = null;

    try {
      if (StringUtil.isDefined(dateReminder)) {
        dateReminderDate = DateUtil.stringToDate(dateReminder, this.getLanguage());
      }
    } catch (ParseException e) {
      // ignore
    }

    //Create date reminder
    createResourceDateReminder(pubDetail, dateReminderDate, messageReminder);
  }

  /**
   * Save Or remove the date reminder for the given publication
   *
   * @param pubId the publication
   * @param parameters the new date reminder parameters
   * @throws DateReminderException on error
   */
  public void updatePublicationReminder(String pubId, List<FileItem> parameters)
      throws DateReminderException {
    String dateReminder = FileUploadUtil.getParameter(parameters, "DateReminder");
    String messageReminder = FileUploadUtil.getParameter(parameters, "MessageReminder");

    Date dateReminderDate = null;

    try {
      if (StringUtil.isDefined(dateReminder)) {
        dateReminderDate = DateUtil.stringToDate(dateReminder, this.getLanguage());
      }
    } catch (ParseException e) {
      // ignore
    }

    PublicationDetail pubDetail = getPublicationDetail(pubId);
    PublicationNoteReference publicationNoteReference =
        PublicationNoteReference.fromPublicationDetail(pubDetail);
    PersistentResourceDateReminder actualResourceDateReminder =
        getDateReminderService().get(publicationNoteReference);

    if (actualResourceDateReminder.isDefined()) {

      if (dateReminderDate != null) {//Update reminder

        DateReminderDetail actualDateReminderDetail = actualResourceDateReminder.getDateReminder();
        String actualDateReminder = DateUtil.dateToString(
            actualDateReminderDetail.getDateReminder(), this.getLanguage());
        int processStatus;
        if (!actualDateReminder.equals(dateReminder)) {// the date reminder has been updated
          // the date reminder must be processed by the scheduler
          processStatus = DateReminderDetail.REMINDER_NOT_PROCESSED;
        } else {// the date reminder has not been updated
          // keep the same process status
          processStatus = actualDateReminderDetail.getProcessStatus();
        }

        DateReminderDetail dateReminderDetail =
            new DateReminderDetail(dateReminderDate, messageReminder, processStatus,
                actualDateReminderDetail.getCreatorId(), pubDetail.getUpdaterId());
        getDateReminderService().set(publicationNoteReference, dateReminderDetail);

      } else {//Delete reminder
        getDateReminderService().remove(publicationNoteReference);
      }
    } else {//Create reminder
      createResourceDateReminder(pubDetail, dateReminderDate, messageReminder);
    }
  }

  public boolean isTemplatesSelectionEnabledForRole(SilverpeasRole role) {
    return !isXMLFormEnabledForPublications() && isContentEnabled() &&
        SilverpeasRole.ADMIN.equals(role);
  }

  private String getXMLFormNameForPublications() {
    return getComponentParameterValue("XmlFormForPublis");
  }

  private boolean isXMLFormEnabledForPublications() {
    return StringUtil.isDefined(getXMLFormNameForPublications());
  }

  public Form getXmlFormForPublications() {
    Form formUpdate = null;
    try {
      PublicationTemplateImpl pubTemplate =
          (PublicationTemplateImpl) getTemplateForPublications(true);
      if (pubTemplate != null) {
        formUpdate = pubTemplate.getUpdateForm();
        RecordSet recordSet = pubTemplate.getRecordSet();
        DataRecord theRecord = recordSet.getEmptyRecord();
        formUpdate.setData(theRecord);
      }
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
    }
    return formUpdate;
  }

  /**
   * Gets the XML form to use in a local search. This form is also the default template for the
   * publication contents in the Kmelia instance. Such form can only be set at the Kmelia instance
   * level and it is applied for all the topics.
   * <p>
   * Setting an XML form for the whole Kmelia instance is in opposition with the feature to set one
   * or more XML forms as publication contents for a given folder. Latter is enabled only if there
   * is no global XML form as publication content for the whole Kmelia instance.
   * </p>
   * <p>
   * Because more than one XML form can be set as publication content for a given topic, such XML
   * forms, even if there is only one, cannot be candidates for a local search in the concerned
   * topic.
   * </p>
   *
   * @return the form to use a local search or null if there is no such form.
   */
  public Form getXmlFormSearchForPublications() {
    Form form = null;
    try {
      PublicationTemplateImpl pubTemplate =
          (PublicationTemplateImpl) getTemplateForPublications(false);
      if (pubTemplate != null && pubTemplate.isSearchable()) {
        form = pubTemplate.getSearchForm();
        DataRecord emptyRecord = pubTemplate.getRecordSet().getEmptyRecord();
        form.setData(emptyRecord);
        setSearchCriteria(emptyRecord);
      }
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
    }
    return form;
  }

  private void setSearchCriteria(DataRecord theRecord) {
    if (getSearchContext() != null) {
      QueryDescription queryDescription = getSearchContext().getQueryDescription();
      if (queryDescription != null && queryDescription.getMultiFieldQuery() != null) {
        for (FieldDescription fieldDescription : queryDescription.getMultiFieldQuery()) {
          try {
            Field field = theRecord.getField(extractFieldName(fieldDescription.getFieldName()));
            if (field != null) {
              field.setValue(
                  fieldDescription.getContent().replace(" OR ", "##").replace(" AND ", "##"));
            }
          } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
          }
        }
      }
    }
  }

  private String extractFieldName(String completeFieldName) {
    String[] fieldNameParts = StringUtil.split(completeFieldName, "$$");
    if (fieldNameParts.length == 2) {
      return fieldNameParts[1];
    }
    return "";
  }

  private PublicationTemplate getTemplateForPublications(boolean specificToTopic) {
    PublicationTemplate template = null;
    List<String> forms = getModelUsed(specificToTopic);
    if (forms.size() == 1 && !"WYSIWYG".equals(forms.get(0))) {
      String form = forms.get(0);
      String formShort = form.substring(0, form.indexOf('.'));
      try {
        // register xmlForm to publication
        getPublicationTemplateManager()
            .addDynamicPublicationTemplate(getComponentId() + ":" + formShort, form);

        template = getPublicationTemplateManager()
            .getPublicationTemplate(getComponentId() + ':' + formShort, form);
      } catch (Exception e) {
        SilverLogger.getLogger(this).error(e);
      }
    }
    return template;
  }

  public void saveXMLFormToPublication(PublicationDetail pubDetail, List<FileItem> items,
      boolean forceUpdatePublication) throws SilverpeasException {
    saveXMLFormToPublication(pubDetail, new KmeliaXmlFormUpdateContext(items,
        forceUpdatePublication));
  }

  public boolean saveXMLFormToPublication(PublicationDetail pubDetail,
      final KmeliaXmlFormUpdateContext updateContext) throws SilverpeasException {
    final String xmlFormShortName;
    // Is it the creation of the content or an update ?
    final String infoId = pubDetail.getInfoId();
    if (infoId == null || "0".equals(infoId)) {
      xmlFormShortName = updateContext.getXmlFormShortNameFromItems();

      // The publication have no content
      // We have to register xmlForm to publication
      pubDetail.setInfoId(xmlFormShortName);
      updatePublication(pubDetail);
    } else {
      xmlFormShortName = pubDetail.getInfoId();
      if (!xmlFormShortName.equals(updateContext.getXmlFormShortNameFromItems())) {
        SilverLogger.getLogger(this)
            .error("{0} and {1} mismatch, no update performed for pub {2}", xmlFormShortName,
                updateContext.getXmlFormShortNameFromItems(), pubDetail.getPK());
        return false;
      }
    }

    try {
      final Pair<PublicationTemplate, DataRecord> pubData =
          updateContext.getOrInitializePublicationDataRecordOf(
              pubDetail, getCurrentLanguage());
      final Form form = pubData.getFirst().getUpdateForm();
      final RecordSet set = pubData.getFirst().getRecordSet();
      final DataRecord data = pubData.getSecond();
      final PagesContext context = getPagesContext(pubDetail, updateContext);
      form.update(updateContext.getItems(), data, context);
      set.save(data);
    } catch (Exception e) {
      throw new SilverpeasException("Can't save XML form of publication", e);
    }
    String pubId = pubDetail.getPK().getId();
    ofNullable(FileUploadUtil.getParameter(updateContext.getItems(), "VolatileId"))
        .filter(StringUtil::isDefined)
        // Attaching all documents linked to volatile publication to the persisted one
        .map(v -> Pair.of(v, AttachmentServiceProvider.getAttachmentService()
            .moveAllDocuments(getPublicationPK(v).toResourceReference(),
                pubDetail.getPK().toResourceReference())))
        .filter(p -> !p.getSecond().isEmpty())
        // Change images path in wysiwyg
        .ifPresent(p -> WysiwygController.wysiwygPlaceHaveChanged(getComponentId(), p.getFirst(),
            getComponentId(), pubId));
    if (updateContext.isForceUpdatePublication()) {
      // update publication to change updateDate and updaterId
      updatePublication(pubDetail);
    }
    return true;
  }

  private PagesContext getPagesContext(PublicationDetail pubDetail,
      KmeliaXmlFormUpdateContext updateContext) {
    final PagesContext context = new PagesContext();
    context.setLanguage(getLanguage());
    context.setComponentId(getComponentId());
    context.setUserId(getUserId());
    context.setEncoding(Charsets.UTF_8.name());
    if (!isKmaxMode) {
      context.setNodeId(getCurrentFolderId());
    }
    context.setObjectId(pubDetail.getPK().getId());
    if (updateContext.isForceUpdatePublication()) {
      // case of a modification of the publication
      context.setContentLanguage(getContentLanguage());
    } else {
      // the publication is just created
      context.setContentLanguage(pubDetail.getLanguage());
    }
    if (updateContext.isBatchProcessing()) {
      context.setUpdatePolicy(PagesContext.ON_UPDATE_IGNORE_EMPTY_VALUES);
    }
    return context;
  }

  public void saveXMLForm(List<FileItem> items, boolean forceUpdatePublication)
      throws SilverpeasException {
    if (isCloneNeeded()) {
      clonePublication();
    }

    final String currentLanguageCopy = getCurrentLanguage();
    PublicationDetail pubDetail = getSessionPubliOrClone().getDetail();
    saveXMLFormToPublication(pubDetail, items, forceUpdatePublication);
    setCurrentLanguage(currentLanguageCopy);
  }

  public PublicationDetail prepareNewPublication() {
    String volatileId = newVolatileIntegerIdentifierOn(getComponentId());
    return PublicationDetail.builder()
        .setPk(getPublicationPK(volatileId))
        .build();
  }

  /**
   * Backups the current active content language in order to update it for an operation.
   */
  public void backupCurrentLanguage() {
    this.cachedCurrentLanguage = currentLanguage;
  }

  /**
   * Restores the current content language as it was before its backup.
   */
  public void restoreCurrentLanguage() {
    if (this.cachedCurrentLanguage != null) {
      this.currentLanguage = this.cachedCurrentLanguage;
      this.cachedCurrentLanguage = null;
    }
  }

  public enum CLIPBOARD_STATE {
    IS_EMPTY, HAS_COPIES, HAS_CUTS, HAS_COPIES_AND_CUTS
  }

  private void processBreadcrumb(String id) {
    if (KmeliaHelper.isSpecialFolder(id)) {
      // special folders aren't nodes and hence they have no path
      return;
    }
    Collection<NodeDetail> pathColl = getTopicPath(id);
    String linkedPathString = displayPath(pathColl, true, 3);
    String pathString = displayPath(pathColl, false, 3);
    setSessionPath(linkedPathString);
    setSessionPathString(pathString);
  }

  private String getPublicationListSessionCacheKey() {
    return KmeliaSessionController.class.getSimpleName() + "#" + getComponentId() +
        "#publicationList";
  }

  public void saveXMLFormOfSelectedPublications(List<FileItem> items) {
    final WebMessager messager = WebMessager.getInstance();
    final Map<PublicationPK, Pair<KmeliaPublication, KmeliaPublication>> authorizedPublicationsOrClonesByPubOrClonePks =
        getPublicationsForModification(selectedPublicationPKs)
            .stream()
            .filter(p -> Objects.equals(p.getFirst().getComponentInstanceId(), getComponentId()))
            .collect(toMap(p -> p.getSecond() != null ? p.getSecond().getPk() :
                p.getFirst().getPk(), p -> p));
    if (authorizedPublicationsOrClonesByPubOrClonePks.isEmpty()) {
      messager.addSuccess(getMultilang().getString("kmelia.publications.batch.update.none"));
      return;
    }
    final Map<PublicationPK, PublicationDetail> publicationOrCloneCache =
        authorizedPublicationsOrClonesByPubOrClonePks
            .values()
            .stream()
            .map(p ->
                p.getSecond() != null ?
                    p.getSecond() :
                    p.getFirst())
            .collect(toMap(KmeliaPublication::getPk, KmeliaPublication::getDetail));
    final KmeliaXmlFormUpdateContext updateContext =
        new KmeliaXmlFormUpdateContext(items, true).batchProcessing();
    try {
      SimulationActionProcessProcessor.get()
          .withContext(s -> s.getSourcePKs().addAll(publicationOrCloneCache.keySet()))
          .listElementsWith(() -> new KmeliaPublicationBatchSimulationElementLister(publicationOrCloneCache,
              updateContext, getUserDetail()))
          .byAction(() -> ActionType.UPDATE)
          .toTargets(t -> t.getTargetPKs().add(getCurrentFolderPK()))
          .setLanguage(this::getCurrentLanguage)
          .execute(() -> {
            final List<PublicationPK> success = new ArrayList<>();
            final List<PublicationPK> fail = new ArrayList<>();
            publicationOrCloneCache.forEach((k, p) -> {
              final PublicationPK selectedPk =
                  authorizedPublicationsOrClonesByPubOrClonePks.get(k).getFirst().getPk();
              try {
                Optional.of(saveXMLFormToPublication(p, updateContext))
                    .filter(Boolean.TRUE::equals)
                    .ifPresentOrElse(t -> success.add(selectedPk), () -> fail.add(selectedPk));
              } catch (Exception e) {
                SilverLogger.getLogger(this).error("Can't save content of publication #" + p.getId(), e);
                fail.add(selectedPk);
              }
            });
            if (fail.isEmpty()) {
              messager.addSuccess(
                  getMultilang().getString("kmelia.publications.batch.update.success"),
                  success.size());
            } else {
              messager.addError(
                  getMultilang().getString("kmelia.publications.batch.update.fail"),
                  fail.size());
            }
            selectedPublicationPKs.removeAll(success);
            return null;
          });
    } catch (Exception e) {
      SilverpeasTransverseErrorUtil.stopTransverseErrorIfAny(new SilverpeasRuntimeException(e));
    }
  }

  public boolean isAllPublicationsListSelected() {
    return allPublicationsListSelected;
  }

  public void setAllPublicationsListSelected(final boolean allPublicationsListSelected) {
    this.allPublicationsListSelected = allPublicationsListSelected;
  }

  public void selectAllPublications(boolean selected) {
    resetSelectedPublicationPKs();
    setAllPublicationsListSelected(selected);
    if (!selected) {
      return;
    }

    List<KmeliaPublication> publications = getSessionPublicationsList();
    for (KmeliaPublication publication : publications) {
      selectedPublicationPKs.add(publication.getPk());
    }
  }


}