content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library 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
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.ui.dialogs;
  
  import org.opencms.configuration.CmsSchedulerConfiguration;
  import org.opencms.db.CmsResourceState;
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsProject;
  import org.opencms.file.CmsResource;
  import org.opencms.file.CmsResourceFilter;
  import org.opencms.lock.CmsLock;
  import org.opencms.main.CmsContextInfo;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.scheduler.CmsScheduledJobInfo;
  import org.opencms.scheduler.jobs.CmsPublishScheduledJob;
  import org.opencms.security.CmsPermissionSet;
  import org.opencms.security.CmsRole;
  import org.opencms.ui.CmsVaadinUtils;
  import org.opencms.ui.I_CmsDialogContext;
  import org.opencms.ui.components.CmsBasicDialog;
  import org.opencms.ui.components.CmsDateField;
  import org.opencms.ui.components.CmsOkCancelActionHandler;
  import org.opencms.util.CmsDateUtil;
  import org.opencms.util.CmsUUID;
  import org.opencms.workplace.commons.Messages;
  
  import java.text.DateFormat;
  import java.time.LocalDateTime;
  import java.util.Calendar;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.SortedMap;
  import java.util.TreeMap;
  import java.util.stream.Collectors;
  
  import org.apache.commons.logging.Log;
  
  import com.vaadin.ui.Button;
  import com.vaadin.ui.Button.ClickEvent;
  import com.vaadin.ui.Button.ClickListener;
  import com.vaadin.ui.CheckBox;
  import com.vaadin.ui.FormLayout;
  
  /**
   * The publish schedule dialog.<p>
   */
  public class CmsPublishScheduledDialog extends CmsBasicDialog {
  
      /** The serial version id. */
      private static final long serialVersionUID = 7488454443783670970L;
  
      /** Logger instance for this class. */
      private static final Log LOG = CmsLog.getLog(CmsPublishScheduledDialog.class);
  
      /** The Admin CmsObject. */
      private static CmsObject m_adminCms;
  
      /** The dialog context. */
      private I_CmsDialogContext m_context;
  
      /** The date selection field. */
      private CmsDateField m_dateField;
  
      /** The OK button. */
      private Button m_okButton;
  
      /** The cancel button. */
      private Button m_cancelButton;
  
      /** Include sub-resources check box. */
      private CheckBox m_includeSubResources;
  
      /**
       * Constructor.<p>
       *
       * @param context the dialog context
       */
      public CmsPublishScheduledDialog(I_CmsDialogContext context) {
  
          m_context = context;
          displayResourceInfo(context.getResources());
          FormLayout form = initForm();
          setContent(form);
          m_okButton = new Button(CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0));
          m_okButton.addClickListener(new ClickListener() {
  
              private static final long serialVersionUID = 1L;
  
              public void buttonClick(ClickEvent event) {
  
                  submit();
              }
          });
          addButton(m_okButton);
          m_cancelButton = new Button(
              CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0));
          m_cancelButton.addClickListener(new ClickListener() {
  
              private static final long serialVersionUID = 1L;
  
              public void buttonClick(ClickEvent event) {
  
                  cancel();
              }
          });
          addButton(m_cancelButton);
  
          setActionHandler(new CmsOkCancelActionHandler() {
  
              private static final long serialVersionUID = 1L;
  
              @Override
              protected void cancel() {
  
                  CmsPublishScheduledDialog.this.cancel();
              }
  
              @Override
              protected void ok() {
  
                  submit();
              }
          });
  
          m_dateField.setRangeStart(LocalDateTime.now());
  
      }
  
      /**
       * Sets the admin cms object. This must happen during start up.
       * @param cms the admin cms.
       */
      public static void setAdminCms(CmsObject cms) {
  
          m_adminCms = cms;
      }
  
      /**
       * Cancels the dialog action.<p>
       */
      void cancel() {
  
          m_context.finish(Collections.<CmsUUID> emptyList());
      }
  
      /**
       * Submits the dialog action.<p>
       */
      void submit() {
  
          //        if (!m_dateField.isValid()) {
          //            return;
          //        }
          long current = System.currentTimeMillis();
          Date dateValue = m_dateField.getDate();
          long publishTime = dateValue.getTime();
          if (current > publishTime) {
              m_context.error(
                  new CmsException(Messages.get().container(Messages.ERR_PUBLISH_SCHEDULED_DATE_IN_PAST_1, dateValue)));
          } else {
              try {
                  // get the user cms object
                  CmsObject cms = OpenCms.initCmsObject(m_context.getCms());
  
                  // set the current user site to the admin cms object
                  CmsObject adminCms = getAdminCms();
                  adminCms.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot());
                  CmsProject tmpProject = createTempProject(adminCms, m_context.getResources(), dateValue);
                  // set project as current project
                  adminCms.getRequestContext().setCurrentProject(tmpProject);
                  cms.getRequestContext().setCurrentProject(tmpProject);
  
                  Set<CmsUUID> changeIds = new HashSet<CmsUUID>();
                  Set<CmsResource> needToUnlockLater = new HashSet<>();
                  for (CmsResource resource : m_context.getResources()) {
                      addToTempProject(adminCms, cms, resource, tmpProject, needToUnlockLater);
                      if (resource.isFolder() && m_includeSubResources.getValue().booleanValue()) {
                          List<CmsResource> subResources = cms.readResources(
                              resource,
                              CmsResourceFilter.ONLY_VISIBLE.addExcludeState(CmsResourceState.STATE_UNCHANGED),
                              true);
                          for (CmsResource sub : subResources) {
                              // check publish permissions on sub resource
                              if (cms.hasPermissions(
                                  sub,
                                  CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
                                  false,
                                  CmsResourceFilter.ALL)) {
                                  addToTempProject(adminCms, cms, sub, tmpProject, needToUnlockLater);
                              }
                          }
                      }
  
                      changeIds.add(resource.getStructureId());
                  }
                  // create a new scheduled job
                  CmsScheduledJobInfo job = new CmsScheduledJobInfo();
                  // the job name
                  String jobName = tmpProject.getName();
                  jobName = jobName.replace("&#47;", "/");
                  // set the job parameters
                  job.setJobName(jobName);
                  job.setClassName("org.opencms.scheduler.jobs.CmsPublishScheduledJob");
                  // create the cron expression
                  Calendar calendar = Calendar.getInstance();
                  calendar.setTime(dateValue);
                  String cronExpr = ""
                      + calendar.get(Calendar.SECOND)
                      + " "
                      + calendar.get(Calendar.MINUTE)
                      + " "
                      + calendar.get(Calendar.HOUR_OF_DAY)
                      + " "
                      + calendar.get(Calendar.DAY_OF_MONTH)
                      + " "
                      + (calendar.get(Calendar.MONTH) + 1)
                      + " "
                      + "?"
                      + " "
                      + calendar.get(Calendar.YEAR);
                  // set the cron expression
                  job.setCronExpression(cronExpr);
                  // set the job active
                  job.setActive(true);
                  // create the context info
                  CmsContextInfo contextInfo = new CmsContextInfo();
                  contextInfo.setProjectName(tmpProject.getName());
                  contextInfo.setUserName(adminCms.getRequestContext().getCurrentUser().getName());
                  // create the job schedule parameter
                  SortedMap<String, String> params = new TreeMap<String, String>();
                  // the user to send mail to
                  String userName = m_context.getCms().getRequestContext().getCurrentUser().getName();
                  params.put(CmsPublishScheduledJob.PARAM_USER, userName);
                  // the job name
                  params.put(CmsPublishScheduledJob.PARAM_JOBNAME, jobName);
                  // the link check
                  params.put(CmsPublishScheduledJob.PARAM_LINKCHECK, "true");
                  String unlockIdList = needToUnlockLater.stream().map(res -> res.getStructureId().toString()).collect(
                      Collectors.joining(","));
                  params.put(CmsPublishScheduledJob.PARAM_UNLOCK_LIST, unlockIdList);
                  // add the job schedule parameter
                  job.setParameters(params);
                  // add the context info to the scheduled job
                  job.setContextInfo(contextInfo);
                  // add the job to the scheduled job list
                  OpenCms.getScheduleManager().scheduleJob(adminCms, job);
                  // update the XML configuration
                  OpenCms.writeConfiguration(CmsSchedulerConfiguration.class);
                  m_context.finish(changeIds);
              } catch (CmsException ex) {
                  LOG.error("Error performing publish scheduled dialog operation.", ex);
                  m_context.error(ex);
              }
          }
  
      }
  
      /**
       * Adds the given resource to the temporary project.<p>
       *
       * @param adminCms the admin cms context
       * @param userCms the user cms context
       * @param resource the resource
       * @param tmpProject the temporary project
       * @param needToUnlockLater a set to which this method adds resources that should be explicitly unlocked later
       *
       * @throws CmsException in case something goes wrong
       */
      private void addToTempProject(
          CmsObject adminCms,
          CmsObject userCms,
          CmsResource resource,
          CmsProject tmpProject,
          Set<CmsResource> needToUnlockLater)
      throws CmsException {
  
          // copy the resource to the project
          adminCms.copyResourceToProject(resource);
  
          // lock the resource in the current project
          CmsLock lock = userCms.getLock(resource);
          // prove is current lock from current but not in current project
          if ((lock != null)
              && lock.isOwnedBy(userCms.getRequestContext().getCurrentUser())
              && !lock.isOwnedInProjectBy(
                  userCms.getRequestContext().getCurrentUser(),
                  userCms.getRequestContext().getCurrentProject())) {
              // file is locked by current user but not in current project
              // change the lock from this file
              userCms.changeLock(resource);
          }
          // lock resource from current user in current project
          userCms.lockResource(resource);
          if (resource.getState().isUnchanged()) {
              needToUnlockLater.add(resource);
          }
          // get current lock
          lock = userCms.getLock(resource);
      }
  
      /**
       * Creates the publish project's name for a given root path and publish date.<p>
       *
       * @param rootPath the publish resource's root path
       * @param date the publish date
       *
       * @return the publish project name
       */
      private String computeProjectName(String rootPath, Date date) {
  
          // create the temporary project, which is deleted after publishing
          // the publish scheduled date in project name
          String dateTime = CmsDateUtil.getDateTime(date, DateFormat.SHORT, getLocale());
          // the resource name to publish scheduled
          String projectName = CmsVaadinUtils.getMessageText(
              Messages.GUI_PUBLISH_SCHEDULED_PROJECT_NAME_2,
              rootPath,
              dateTime);
          // the HTML encoding for slashes is necessary because of the slashes in english date time format
          // in project names slahes are not allowed, because these are separators for organizaional units
          projectName = projectName.replace("/", "&#47;");
          while (projectName.length() > 190) {
              rootPath = "..." + rootPath.substring(5, rootPath.length());
              projectName = computeProjectName(rootPath, date);
          }
          return projectName;
      }
  
      /**
       * Creates the temporary project.<p>
       *
       * @param adminCms the admin cms context
       * @param resources the resources
       * @param date the publish date
       *
       * @return the project
       *
       * @throws CmsException in case writing the project fails
       */
      private CmsProject createTempProject(CmsObject adminCms, List<CmsResource> resources, Date date)
      throws CmsException {
  
          CmsProject tmpProject;
  
          String rootPath = resources.get(0).getRootPath();
          if (resources.size() > 1) {
              rootPath = CmsResource.getParentFolder(rootPath);
          }
          String projectName = computeProjectName(rootPath, date);
  
          try {
              // create the project
              tmpProject = adminCms.createProject(
                  projectName,
                  "",
                  CmsRole.WORKPLACE_USER.getGroupName(),
                  CmsRole.PROJECT_MANAGER.getGroupName(),
                  CmsProject.PROJECT_TYPE_TEMPORARY);
          } catch (CmsException e) {
              String resName = CmsResource.getName(rootPath);
              if (resName.length() > 64) {
                  resName = resName.substring(0, 64) + "...";
              }
              // use UUID to make sure the project name is still unique
              projectName = computeProjectName(resName, date) + " [" + new CmsUUID() + "]";
              // create the project
              tmpProject = adminCms.createProject(
                  projectName,
                  "",
                  CmsRole.WORKPLACE_USER.getGroupName(),
                  CmsRole.PROJECT_MANAGER.getGroupName(),
                  CmsProject.PROJECT_TYPE_TEMPORARY);
          }
          // make the project invisible for all users
          tmpProject.setHidden(true);
          // write the project to the database
          adminCms.writeProject(tmpProject);
          return tmpProject;
      }
  
      /**
       * Returns a copy of the admin cms object.
       * @return a copy of the admin cms object.
       * @throws CmsException thrown if copying the cms object fails.
       */
      private CmsObject getAdminCms() throws CmsException {
  
          return OpenCms.initCmsObject(m_adminCms);
      }
  
      /**
       * Checks whether the resources list contains any folders.<p>
       *
       * @return <code>true</code> if the resources list contains any folders
       */
      private boolean hasFolders() {
  
          for (CmsResource resource : m_context.getResources()) {
              if (resource.isFolder()) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Initializes the form fields.<p>
       *
       * @return the form component
       */
      private FormLayout initForm() {
  
          FormLayout form = new FormLayout();
          form.setWidth("100%");
          m_dateField = new CmsDateField();
          m_dateField.setCaption(
              CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_LABEL_DATE_PUBLISH_SCHEDULED_0));
          form.addComponent(m_dateField);
          m_includeSubResources = new CheckBox(
              CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_PUBLISH_MULTI_SUBRESOURCES_0));
          if (hasFolders()) {
              form.addComponent(m_includeSubResources);
          }
  
          return form;
      }
  }
  