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.availability;
  
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsProperty;
  import org.opencms.file.CmsPropertyDefinition;
  import org.opencms.file.CmsResource;
  import org.opencms.file.CmsResourceFilter;
  import org.opencms.file.types.CmsResourceTypeXmlContent;
  import org.opencms.file.types.I_CmsResourceType;
  import org.opencms.gwt.shared.CmsPrincipalBean;
  import org.opencms.loader.CmsLoaderException;
  import org.opencms.lock.CmsLockActionRecord;
  import org.opencms.lock.CmsLockActionRecord.LockChange;
  import org.opencms.lock.CmsLockException;
  import org.opencms.lock.CmsLockUtil;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.security.CmsAccessControlEntry;
  import org.opencms.security.I_CmsPrincipal;
  import org.opencms.ui.A_CmsUI;
  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.ui.components.CmsResourceInfo;
  import org.opencms.util.CmsStringUtil;
  import org.opencms.util.CmsUUID;
  import org.opencms.workplace.CmsWorkplace;
  import org.opencms.workplace.CmsWorkplaceSettings;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  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.Panel;
  import com.vaadin.v7.data.Property.ValueChangeEvent;
  import com.vaadin.v7.data.Property.ValueChangeListener;
  import com.vaadin.v7.data.Validator;
  import com.vaadin.v7.ui.CheckBox;
  import com.vaadin.v7.ui.TextField;
  import com.vaadin.v7.ui.VerticalLayout;
  
  /**
   * Availability dialog.<p>
   */
  public class CmsAvailabilityDialog extends CmsBasicDialog {
  
      /** Preference key for the option to automatically include children. */
      public static final String PREF_AVAILABILITY_INCLUDE_CHILDREN_DEFAULT = "availabilityIncludeChildrenDefault";
  
      /** Logger for this class. */
      private static final Log LOG = CmsLog.getLog(CmsAvailabilityDialog.class);
  
      /** Serial version id. */
      private static final long serialVersionUID = 1L;
  
      /** Availability info. */
      private CmsAvailabilityInfoBean m_availabilityInfo;
  
      /** The cancel button. */
      private Button m_cancelButton;
  
      /** The dialog context. */
      private I_CmsDialogContext m_dialogContext;
  
      /** Date field. */
      private CmsDateField m_expiredField;
  
      /** Initial value for 'notification enabled. */
      private Boolean m_initialNotificationEnabled = Boolean.FALSE;
  
      /** Initial value for notification interval. */
      private String m_initialNotificationInterval = "";
  
      /** 'Modify siblings' check box. */
      private CheckBox m_modifySiblingsField;
  
      /**  'enable notification' check box. */
      private CheckBox m_notificationEnabledField;
  
      /** Field for the notification interval. */
      private TextField m_notificationIntervalField;
  
      /** Panel for notifications. */
      private Panel m_notificationPanel;
  
      /** OK button. */
      private Button m_okButton;
  
      /** Date field. */
      private CmsDateField m_releasedField;
  
      /** Option to reset the expiration. */
      private CheckBox m_resetExpired;
  
      /** Option to reset the relase date. */
      private CheckBox m_resetReleased;
  
      /** Container for responsibles widgets. */
      private VerticalLayout m_responsiblesContainer;
  
      /** Panel for 'Responsibles'. */
      private Panel m_responsiblesPanel;
  
      /** Option to enable subresource modification. */
      private CheckBox m_subresourceModificationField;
  
      /**
       * Creates a new instance.<p>
       *
       * @param dialogContext the dialog context
       */
      public CmsAvailabilityDialog(I_CmsDialogContext dialogContext) {
  
          super();
          m_dialogContext = dialogContext;
          CmsObject cms = dialogContext.getCms();
          CmsVaadinUtils.readAndLocalizeDesign(
              this,
              OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()),
              null);
          List<CmsResource> resources = dialogContext.getResources();
          m_notificationIntervalField.addValidator(new Validator() {
  
              /** Serial version id. */
              private static final long serialVersionUID = 1L;
  
              public void validate(Object value) throws InvalidValueException {
  
                  String strValue = ((String)value).trim();
                  if (!strValue.matches("[0-9]*")) {
                      throw new InvalidValueException(
                          CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_VALIDATOR_EMPTY_OR_NUMBER_0));
  
                  }
  
              }
          });
  
          boolean hasSiblings = false;
          for (CmsResource resource : m_dialogContext.getResources()) {
              hasSiblings |= resource.getSiblingCount() > 1;
              if (hasSiblings) {
                  break;
              }
          }
          m_modifySiblingsField.setVisible(hasSiblings);
  
          if (resources.size() == 1) {
              CmsResource onlyResource = resources.get(0);
              if (onlyResource.getDateReleased() != CmsResource.DATE_RELEASED_DEFAULT) {
                  m_releasedField.setDate(new Date(onlyResource.getDateReleased()));
              }
              if (onlyResource.getDateExpired() != CmsResource.DATE_EXPIRED_DEFAULT) {
                  m_expiredField.setDate(new Date(onlyResource.getDateExpired()));
              }
              initNotification();
              Map<CmsPrincipalBean, String> responsibles = m_availabilityInfo.getResponsibles();
              if (!responsibles.isEmpty()) {
                  m_responsiblesPanel.setVisible(true);
                  m_notificationPanel.setVisible(true);
                  for (Map.Entry<CmsPrincipalBean, String> entry : responsibles.entrySet()) {
                      CmsPrincipalBean principal = entry.getKey();
                      String icon = principal.isGroup()
                      ? CmsWorkplace.getResourceUri("buttons/group.png")
                      : CmsWorkplace.getResourceUri("buttons/user.png");
                      String subtitle = "";
                      if (entry.getValue() != null) {
                          subtitle = cms.getRequestContext().removeSiteRoot(entry.getValue());
                      }
                      CmsResourceInfo infoWidget = new CmsResourceInfo(entry.getKey().getName(), subtitle, icon);
                      m_responsiblesContainer.addComponent(infoWidget);
  
                  }
              }
          } else {
              boolean showNotification = false;
              resourceLoop: for (CmsResource resource : resources) {
                  try {
                      List<CmsAccessControlEntry> aces = cms.getAccessControlEntries(cms.getSitePath(resource));
                      for (CmsAccessControlEntry ace : aces) {
                          if (ace.isResponsible()) {
                              showNotification = true;
                              break resourceLoop;
                          }
                      }
                  } catch (CmsException e) {
                      LOG.error(e.getLocalizedMessage(), e);
                  }
              }
              m_notificationPanel.setVisible(showNotification);
          }
          m_notificationEnabledField.setValue(m_initialNotificationEnabled);
          m_notificationIntervalField.setValue(m_initialNotificationInterval);
          boolean hasFolders = false;
          for (CmsResource resource : resources) {
              if (resource.isFolder()) {
                  hasFolders = true;
              }
          }
  
          CmsWorkplaceSettings wpSettings = A_CmsUI.get().getWorkplaceSettings();
          boolean includeChildren = Boolean.parseBoolean(
              wpSettings.getUserSettings().getAdditionalPreference(PREF_AVAILABILITY_INCLUDE_CHILDREN_DEFAULT, true));
          m_subresourceModificationField.setVisible(hasFolders);
          m_subresourceModificationField.setValue(Boolean.valueOf(includeChildren));
  
          initResetCheckbox(m_resetReleased, m_releasedField);
          initResetCheckbox(m_resetExpired, m_expiredField);
          m_okButton.addClickListener(new ClickListener() {
  
              private static final long serialVersionUID = 1L;
  
              public void buttonClick(ClickEvent event) {
  
                  submit();
              }
  
          });
  
          m_cancelButton.addClickListener(new ClickListener() {
  
              private static final long serialVersionUID = 1L;
  
              public void buttonClick(ClickEvent event) {
  
                  cancel();
              }
          });
          displayResourceInfo(m_dialogContext.getResources());
  
          setActionHandler(new CmsOkCancelActionHandler() {
  
              private static final long serialVersionUID = 1L;
  
              @Override
              protected void cancel() {
  
                  CmsAvailabilityDialog.this.cancel();
              }
  
              @Override
              protected void ok() {
  
                  submit();
              }
          });
      }
  
      /**
       * Initializes the values for the notification widgets.<p>
       */
      public void initNotification() {
  
          if (m_dialogContext.getResources().size() == 1) {
              CmsResource resource = m_dialogContext.getResources().get(0);
              try {
                  m_availabilityInfo = getAvailabilityInfo(A_CmsUI.getCmsObject(), resource);
                  m_initialNotificationInterval = "" + m_availabilityInfo.getNotificationInterval();
                  m_initialNotificationEnabled = Boolean.valueOf(m_availabilityInfo.isNotificationEnabled());
              } catch (CmsLoaderException e) {
                  LOG.error(e.getLocalizedMessage(), e);
              } catch (CmsException e) {
                  LOG.error(e.getLocalizedMessage(), e);
              }
  
          }
      }
  
      /**
       * Actually performs the availability change.<p>
       *
       * @return the ids of the changed resources
       *
       * @throws CmsException if something goes wrong
       */
      protected List<CmsUUID> changeAvailability() throws CmsException {
  
          Date released = m_releasedField.getDate();
          Date expired = m_expiredField.getDate();
          boolean resetReleased = m_resetReleased.getValue().booleanValue();
          boolean resetExpired = m_resetExpired.getValue().booleanValue();
          boolean modifySubresources = m_subresourceModificationField.getValue().booleanValue();
          List<CmsUUID> changedIds = new ArrayList<CmsUUID>();
          for (CmsResource resource : m_dialogContext.getResources()) {
              changeAvailability(resource, released, resetReleased, expired, resetExpired, modifySubresources);
              changedIds.add(resource.getStructureId());
          }
  
          String notificationInterval = m_notificationIntervalField.getValue().trim();
          int notificationIntervalInt = 0;
          try {
              notificationIntervalInt = Integer.parseInt(notificationInterval);
          } catch (NumberFormatException e) {
              LOG.warn(e.getLocalizedMessage(), e);
          }
          Boolean notificationEnabled = m_notificationEnabledField.getValue();
          boolean notificationSettingsUnchanged = notificationInterval.equals(m_initialNotificationInterval)
              && notificationEnabled.equals(m_initialNotificationEnabled);
  
          CmsObject cms = A_CmsUI.getCmsObject();
          if (!notificationSettingsUnchanged) {
              for (CmsResource resource : m_dialogContext.getResources()) {
                  performSingleResourceNotification(
                      A_CmsUI.getCmsObject(),
                      cms.getSitePath(resource),
                      notificationEnabled.booleanValue(),
                      notificationIntervalInt,
                      m_modifySiblingsField.getValue().booleanValue());
              }
  
          }
          return changedIds;
      }
  
      /**
       * Cancels the dialog.<p>
       */
      void cancel() {
  
          m_dialogContext.finish(new ArrayList<CmsUUID>());
      }
  
      /**
       * Returns the availability info.<p>
       *
       * @param cms the cms context
       * @param res the resource
       *
       * @return the info
       *
       * @throws CmsException if reading the info fails
       */
      CmsAvailabilityInfoBean getAvailabilityInfo(CmsObject cms, CmsResource res) throws CmsException {
  
          CmsAvailabilityInfoBean result = new CmsAvailabilityInfoBean();
          I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(res.getTypeId());
          result.setResType(type.getTypeName());
  
          result.setDateReleased(res.getDateReleased());
          result.setDateExpired(res.getDateExpired());
  
          String notificationInterval = cms.readPropertyObject(
              res,
              CmsPropertyDefinition.PROPERTY_NOTIFICATION_INTERVAL,
              false).getValue();
          if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(notificationInterval)) {
              result.setNotificationInterval(Integer.valueOf(notificationInterval).intValue());
          }
  
          String notificationEnabled = cms.readPropertyObject(
              res,
              CmsPropertyDefinition.PROPERTY_ENABLE_NOTIFICATION,
              false).getValue();
          if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(notificationEnabled)) {
              result.setNotificationEnabled(Boolean.valueOf(notificationEnabled).booleanValue());
          }
  
          result.setHasSiblings(cms.readSiblings(res, CmsResourceFilter.ALL).size() > 1);
  
          result.setResponsibles(getResponsibles(cms, res));
  
          return result;
      }
  
      /**
       * Returns the responsibles.<p>
       *
       * @param cms the cms context
       * @param res the resource
       *
       * @return the responsibles
       */
      Map<CmsPrincipalBean, String> getResponsibles(CmsObject cms, CmsResource res) {
  
          Map<CmsPrincipalBean, String> result = new HashMap<CmsPrincipalBean, String>();
          List<CmsResource> parentResources = new ArrayList<CmsResource>();
          // get all parent folders of the current file
          try {
              parentResources = cms.readPath(res, CmsResourceFilter.IGNORE_EXPIRATION);
          } catch (CmsException e) {
              LOG.error(e.getLocalizedMessage(), e);
          }
  
          for (CmsResource resource : parentResources) {
              String storedSiteRoot = cms.getRequestContext().getSiteRoot();
              String sitePath = cms.getRequestContext().removeSiteRoot(resource.getRootPath());
              try {
                  cms.getRequestContext().setSiteRoot("/");
                  List<CmsAccessControlEntry> entries = cms.getAccessControlEntries(resource.getRootPath(), false);
                  for (CmsAccessControlEntry ace : entries) {
                      if (ace.isResponsible()) {
                          I_CmsPrincipal principal = cms.lookupPrincipal(ace.getPrincipal());
                          if (principal != null) {
                              CmsPrincipalBean prinBean = new CmsPrincipalBean(
                                  principal.getName(),
                                  principal.getDescription(),
                                  principal.isGroup());
                              if (!resource.getRootPath().equals(res.getRootPath())) {
                                  if (resource.getRootPath().startsWith(storedSiteRoot)) {
                                      result.put(prinBean, sitePath);
                                  } else {
                                      result.put(prinBean, resource.getRootPath());
                                  }
                              } else {
                                  result.put(prinBean, null);
                              }
                          }
                      }
                  }
              } catch (CmsException e) {
                  LOG.info(
                      "Problem with reading responsible users for "
                          + resource.getName()
                          + " : "
                          + e.getLocalizedMessage(),
                      e);
              } finally {
                  cms.getRequestContext().setSiteRoot(storedSiteRoot);
              }
          }
          return result;
      }
  
      /**
       * Submits the dialog.<p>
       */
      void submit() {
  
          try {
              m_dialogContext.finish(changeAvailability());
          } catch (Throwable t) {
              m_dialogContext.error(t);
          }
      }
  
      /**
       * Changes availability.<p>
       *
       * @param resource the resource
       * @param released release date
       * @param resetReleased reset release date
       * @param expired expiration date
       * @param resetExpired reset expiration date
       * @param modifySubresources modify children
       *
       * @throws CmsException if something goes wrong
       */
      private void changeAvailability(
          CmsResource resource,
          Date released,
          boolean resetReleased,
          Date expired,
          boolean resetExpired,
          boolean modifySubresources)
      throws CmsException {
  
          CmsObject cms = m_dialogContext.getCms();
          CmsLockActionRecord lockActionRecord = CmsLockUtil.ensureLock(cms, resource);
          try {
              cms.getRequestContext().setAttribute(
                  CmsResourceTypeXmlContent.ATTR_REVERSE_AVAILABILITY_MAPPING,
                  Boolean.TRUE);
              long newDateReleased;
              if (resetReleased || (released != null)) {
                  newDateReleased = released != null ? released.getTime() : CmsResource.DATE_RELEASED_DEFAULT;
                  cms.setDateReleased(resource, newDateReleased, modifySubresources);
              }
              long newDateExpired;
              if (resetExpired || (expired != null)) {
                  newDateExpired = expired != null ? expired.getTime() : CmsResource.DATE_EXPIRED_DEFAULT;
                  cms.setDateExpired(resource, newDateExpired, modifySubresources);
              }
          } finally {
              cms.getRequestContext().removeAttribute(CmsResourceTypeXmlContent.ATTR_REVERSE_AVAILABILITY_MAPPING);
              if (lockActionRecord.getChange() == LockChange.locked) {
                  try {
                      cms.unlockResource(resource);
                  } catch (CmsLockException e) {
                      LOG.warn(e.getLocalizedMessage(), e);
                  }
              }
  
          }
      }
  
      /**
       * Creates a reset checkbox which can enable / disable a date field.<p>
       *
       * @param box the check box
       * @param field the date field
       */
      private void initResetCheckbox(CheckBox box, final CmsDateField field) {
  
          box.addValueChangeListener(new ValueChangeListener() {
  
              private static final long serialVersionUID = 1L;
  
              public void valueChange(ValueChangeEvent event) {
  
                  Boolean value = (Boolean)(event.getProperty().getValue());
                  if (value.booleanValue()) {
                      field.clear();
                      field.setEnabled(false);
                  } else {
                      field.setEnabled(true);
                  }
              }
          });
      }
  
      /**
       * Performs the notification operations on a single resource.<p>
       *
       * @param cms the CMS context
       * @param resName the VFS path of the resource
       * @param enableNotification if the notification is activated
       * @param notificationInterval the notification interval in days
       * @param modifySiblings flag indicating to include resource siblings
       *
       * @throws CmsException if the availability and notification operations fail
       */
      private void performSingleResourceNotification(
          CmsObject cms,
          String resName,
          boolean enableNotification,
          int notificationInterval,
          boolean modifySiblings)
      throws CmsException {
  
          List<CmsResource> resources = new ArrayList<CmsResource>();
          if (modifySiblings) {
              // modify all siblings of a resource
              resources = cms.readSiblings(resName, CmsResourceFilter.IGNORE_EXPIRATION);
          } else {
              // modify only resource without siblings
              resources.add(cms.readResource(resName, CmsResourceFilter.IGNORE_EXPIRATION));
          }
          Iterator<CmsResource> i = resources.iterator();
          while (i.hasNext()) {
              CmsResource resource = i.next();
              // lock resource if auto lock is enabled
              CmsLockActionRecord lockRecord = CmsLockUtil.ensureLock(cms, resource);
              try {
                  // write notification settings
                  writeProperty(
                      cms,
                      resource,
                      CmsPropertyDefinition.PROPERTY_NOTIFICATION_INTERVAL,
                      String.valueOf(notificationInterval));
                  writeProperty(
                      cms,
                      resource,
                      CmsPropertyDefinition.PROPERTY_ENABLE_NOTIFICATION,
                      String.valueOf(enableNotification));
              } finally {
                  if (lockRecord.getChange() == LockChange.locked) {
                      cms.unlockResource(resource);
                  }
              }
          }
      }
  
      /**
       * Writes a property value for a resource.<p>
       *
       * @param cms the cms context
       * @param resource the path of the resource
       * @param propertyName the name of the property
       * @param propertyValue the new value of the property
       *
       * @throws CmsException if something goes wrong
       */
      private void writeProperty(CmsObject cms, CmsResource resource, String propertyName, String propertyValue)
      throws CmsException {
  
          if (CmsStringUtil.isEmpty(propertyValue)) {
              propertyValue = CmsProperty.DELETE_VALUE;
          }
  
          CmsProperty newProp = new CmsProperty();
          newProp.setName(propertyName);
          CmsProperty oldProp = cms.readPropertyObject(resource, propertyName, false);
          if (oldProp.isNullProperty()) {
              // property value was not already set
              if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) {
                  newProp.setStructureValue(propertyValue);
              } else {
                  newProp.setResourceValue(propertyValue);
              }
          } else {
              if (oldProp.getStructureValue() != null) {
                  newProp.setStructureValue(propertyValue);
                  newProp.setResourceValue(oldProp.getResourceValue());
              } else {
                  newProp.setResourceValue(propertyValue);
              }
          }
  
          newProp.setAutoCreatePropertyDefinition(true);
  
          String oldStructureValue = oldProp.getStructureValue();
          String newStructureValue = newProp.getStructureValue();
          if (CmsStringUtil.isEmpty(oldStructureValue)) {
              oldStructureValue = CmsProperty.DELETE_VALUE;
          }
          if (CmsStringUtil.isEmpty(newStructureValue)) {
              newStructureValue = CmsProperty.DELETE_VALUE;
          }
  
          String oldResourceValue = oldProp.getResourceValue();
          String newResourceValue = newProp.getResourceValue();
          if (CmsStringUtil.isEmpty(oldResourceValue)) {
              oldResourceValue = CmsProperty.DELETE_VALUE;
          }
          if (CmsStringUtil.isEmpty(newResourceValue)) {
              newResourceValue = CmsProperty.DELETE_VALUE;
          }
  
          // change property only if it has been changed
          if (!oldResourceValue.equals(newResourceValue) || !oldStructureValue.equals(newStructureValue)) {
              cms.writePropertyObjects(resource, Collections.singletonList(newProp));
          }
      }
  }
  