001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.dialogs;
029
030import org.opencms.configuration.CmsSchedulerConfiguration;
031import org.opencms.db.CmsResourceState;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProject;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.lock.CmsLock;
037import org.opencms.main.CmsContextInfo;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.scheduler.CmsScheduledJobInfo;
042import org.opencms.scheduler.jobs.CmsPublishScheduledJob;
043import org.opencms.security.CmsPermissionSet;
044import org.opencms.security.CmsRole;
045import org.opencms.ui.CmsVaadinUtils;
046import org.opencms.ui.I_CmsDialogContext;
047import org.opencms.ui.components.CmsBasicDialog;
048import org.opencms.ui.components.CmsDateField;
049import org.opencms.ui.components.CmsOkCancelActionHandler;
050import org.opencms.util.CmsDateUtil;
051import org.opencms.util.CmsUUID;
052import org.opencms.workplace.CmsWorkplaceAction;
053import org.opencms.workplace.commons.Messages;
054
055import java.text.DateFormat;
056import java.time.LocalDateTime;
057import java.util.Calendar;
058import java.util.Collections;
059import java.util.Date;
060import java.util.HashSet;
061import java.util.List;
062import java.util.Set;
063import java.util.SortedMap;
064import java.util.TreeMap;
065
066import org.apache.commons.logging.Log;
067
068import com.vaadin.ui.Button;
069import com.vaadin.ui.Button.ClickEvent;
070import com.vaadin.ui.Button.ClickListener;
071import com.vaadin.ui.CheckBox;
072import com.vaadin.ui.FormLayout;
073
074/**
075 * The publish schedule dialog.<p>
076 */
077public class CmsPublishScheduledDialog extends CmsBasicDialog {
078
079    /** The serial version id. */
080    private static final long serialVersionUID = 7488454443783670970L;
081
082    /** Logger instance for this class. */
083    private static final Log LOG = CmsLog.getLog(CmsPublishScheduledDialog.class);
084
085    /** The dialog context. */
086    private I_CmsDialogContext m_context;
087
088    /** The date selection field. */
089    private CmsDateField m_dateField;
090
091    /** The OK button. */
092    private Button m_okButton;
093
094    /** The cancel button. */
095    private Button m_cancelButton;
096
097    /** Include sub-resources check box. */
098    private CheckBox m_includeSubResources;
099
100    /**
101     * Constructor.<p>
102     *
103     * @param context the dialog context
104     */
105    public CmsPublishScheduledDialog(I_CmsDialogContext context) {
106
107        m_context = context;
108        displayResourceInfo(context.getResources());
109        FormLayout form = initForm();
110        setContent(form);
111        m_okButton = new Button(CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0));
112        m_okButton.addClickListener(new ClickListener() {
113
114            private static final long serialVersionUID = 1L;
115
116            public void buttonClick(ClickEvent event) {
117
118                submit();
119            }
120        });
121        addButton(m_okButton);
122        m_cancelButton = new Button(
123            CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0));
124        m_cancelButton.addClickListener(new ClickListener() {
125
126            private static final long serialVersionUID = 1L;
127
128            public void buttonClick(ClickEvent event) {
129
130                cancel();
131            }
132        });
133        addButton(m_cancelButton);
134
135        setActionHandler(new CmsOkCancelActionHandler() {
136
137            private static final long serialVersionUID = 1L;
138
139            @Override
140            protected void cancel() {
141
142                CmsPublishScheduledDialog.this.cancel();
143            }
144
145            @Override
146            protected void ok() {
147
148                submit();
149            }
150        });
151
152        m_dateField.setRangeStart(LocalDateTime.now());
153
154    }
155
156    /**
157     * Cancels the dialog action.<p>
158     */
159    void cancel() {
160
161        m_context.finish(Collections.<CmsUUID> emptyList());
162    }
163
164    /**
165     * Submits the dialog action.<p>
166     */
167    void submit() {
168
169        //        if (!m_dateField.isValid()) {
170        //            return;
171        //        }
172        long current = System.currentTimeMillis();
173        Date dateValue = m_dateField.getDate();
174        long publishTime = dateValue.getTime();
175        if (current > publishTime) {
176            m_context.error(
177                new CmsException(Messages.get().container(Messages.ERR_PUBLISH_SCHEDULED_DATE_IN_PAST_1, dateValue)));
178        } else {
179            try {
180                // make copies from the admin cmsobject and the user cmsobject
181                // get the admin cms object
182                CmsWorkplaceAction action = CmsWorkplaceAction.getInstance();
183                CmsObject cmsAdmin = action.getCmsAdminObject();
184                // get the user cms object
185                CmsObject cms = OpenCms.initCmsObject(m_context.getCms());
186
187                // set the current user site to the admin cms object
188                cmsAdmin.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot());
189                CmsProject tmpProject = createTempProject(cmsAdmin, m_context.getResources(), dateValue);
190                // set project as current project
191                cmsAdmin.getRequestContext().setCurrentProject(tmpProject);
192                cms.getRequestContext().setCurrentProject(tmpProject);
193
194                Set<CmsUUID> changeIds = new HashSet<CmsUUID>();
195                for (CmsResource resource : m_context.getResources()) {
196                    addToTempProject(cmsAdmin, cms, resource, tmpProject);
197                    if (resource.isFolder() && m_includeSubResources.getValue().booleanValue()) {
198                        List<CmsResource> subResources = cms.readResources(
199                            resource,
200                            CmsResourceFilter.ONLY_VISIBLE.addExcludeState(CmsResourceState.STATE_UNCHANGED),
201                            true);
202                        for (CmsResource sub : subResources) {
203                            // check publish permissions on sub resource
204                            if (cms.hasPermissions(
205                                sub,
206                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
207                                false,
208                                CmsResourceFilter.ALL)) {
209                                addToTempProject(cmsAdmin, cms, sub, tmpProject);
210                            }
211                        }
212                    }
213
214                    changeIds.add(resource.getStructureId());
215                }
216                // create a new scheduled job
217                CmsScheduledJobInfo job = new CmsScheduledJobInfo();
218                // the job name
219                String jobName = tmpProject.getName();
220                jobName = jobName.replace("&#47;", "/");
221                // set the job parameters
222                job.setJobName(jobName);
223                job.setClassName("org.opencms.scheduler.jobs.CmsPublishScheduledJob");
224                // create the cron expression
225                Calendar calendar = Calendar.getInstance();
226                calendar.setTime(dateValue);
227                String cronExpr = ""
228                    + calendar.get(Calendar.SECOND)
229                    + " "
230                    + calendar.get(Calendar.MINUTE)
231                    + " "
232                    + calendar.get(Calendar.HOUR_OF_DAY)
233                    + " "
234                    + calendar.get(Calendar.DAY_OF_MONTH)
235                    + " "
236                    + (calendar.get(Calendar.MONTH) + 1)
237                    + " "
238                    + "?"
239                    + " "
240                    + calendar.get(Calendar.YEAR);
241                // set the cron expression
242                job.setCronExpression(cronExpr);
243                // set the job active
244                job.setActive(true);
245                // create the context info
246                CmsContextInfo contextInfo = new CmsContextInfo();
247                contextInfo.setProjectName(tmpProject.getName());
248                contextInfo.setUserName(cmsAdmin.getRequestContext().getCurrentUser().getName());
249                // create the job schedule parameter
250                SortedMap<String, String> params = new TreeMap<String, String>();
251                // the user to send mail to
252                String userName = m_context.getCms().getRequestContext().getCurrentUser().getName();
253                params.put(CmsPublishScheduledJob.PARAM_USER, userName);
254                // the job name
255                params.put(CmsPublishScheduledJob.PARAM_JOBNAME, jobName);
256                // the link check
257                params.put(CmsPublishScheduledJob.PARAM_LINKCHECK, "true");
258                // add the job schedule parameter
259                job.setParameters(params);
260                // add the context info to the scheduled job
261                job.setContextInfo(contextInfo);
262                // add the job to the scheduled job list
263                OpenCms.getScheduleManager().scheduleJob(cmsAdmin, job);
264                // update the XML configuration
265                OpenCms.writeConfiguration(CmsSchedulerConfiguration.class);
266                m_context.finish(changeIds);
267            } catch (CmsException ex) {
268                LOG.error("Error performing publish scheduled dialog operation.", ex);
269                m_context.error(ex);
270            }
271        }
272
273    }
274
275    /**
276     * Adds the given resource to the temporary project.<p>
277     *
278     * @param adminCms the admin cms context
279     * @param userCms the user cms context
280     * @param resource the resource
281     * @param tmpProject the temporary project
282     * @throws CmsException in case something goes wrong
283     */
284    private void addToTempProject(CmsObject adminCms, CmsObject userCms, CmsResource resource, CmsProject tmpProject)
285    throws CmsException {
286
287        // copy the resource to the project
288        adminCms.copyResourceToProject(resource);
289
290        // lock the resource in the current project
291        CmsLock lock = userCms.getLock(resource);
292        // prove is current lock from current but not in current project
293        if ((lock != null)
294            && lock.isOwnedBy(userCms.getRequestContext().getCurrentUser())
295            && !lock.isOwnedInProjectBy(
296                userCms.getRequestContext().getCurrentUser(),
297                userCms.getRequestContext().getCurrentProject())) {
298            // file is locked by current user but not in current project
299            // change the lock from this file
300            userCms.changeLock(resource);
301        }
302        // lock resource from current user in current project
303        userCms.lockResource(resource);
304        // get current lock
305        lock = userCms.getLock(resource);
306    }
307
308    /**
309     * Creates the publish project's name for a given root path and publish date.<p>
310     *
311     * @param rootPath the publish resource's root path
312     * @param date the publish date
313     *
314     * @return the publish project name
315     */
316    private String computeProjectName(String rootPath, Date date) {
317
318        // create the temporary project, which is deleted after publishing
319        // the publish scheduled date in project name
320        String dateTime = CmsDateUtil.getDateTime(date, DateFormat.SHORT, getLocale());
321        // the resource name to publish scheduled
322        String projectName = CmsVaadinUtils.getMessageText(
323            Messages.GUI_PUBLISH_SCHEDULED_PROJECT_NAME_2,
324            rootPath,
325            dateTime);
326        // the HTML encoding for slashes is necessary because of the slashes in english date time format
327        // in project names slahes are not allowed, because these are separators for organizaional units
328        projectName = projectName.replace("/", "&#47;");
329        while (projectName.length() > 190) {
330            rootPath = "..." + rootPath.substring(5, rootPath.length());
331            projectName = computeProjectName(rootPath, date);
332        }
333        return projectName;
334    }
335
336    /**
337     * Creates the temporary project.<p>
338     *
339     * @param adminCms the admin cms context
340     * @param resources the resources
341     * @param date the publish date
342     *
343     * @return the project
344     *
345     * @throws CmsException in case writing the project fails
346     */
347    private CmsProject createTempProject(CmsObject adminCms, List<CmsResource> resources, Date date)
348    throws CmsException {
349
350        CmsProject tmpProject;
351
352        String rootPath = resources.get(0).getRootPath();
353        if (resources.size() > 1) {
354            rootPath = CmsResource.getParentFolder(rootPath);
355        }
356        String projectName = computeProjectName(rootPath, date);
357
358        try {
359            // create the project
360            tmpProject = adminCms.createProject(
361                projectName,
362                "",
363                CmsRole.WORKPLACE_USER.getGroupName(),
364                CmsRole.PROJECT_MANAGER.getGroupName(),
365                CmsProject.PROJECT_TYPE_TEMPORARY);
366        } catch (CmsException e) {
367            String resName = CmsResource.getName(rootPath);
368            if (resName.length() > 64) {
369                resName = resName.substring(0, 64) + "...";
370            }
371            // use UUID to make sure the project name is still unique
372            projectName = computeProjectName(resName, date) + " [" + new CmsUUID() + "]";
373            // create the project
374            tmpProject = adminCms.createProject(
375                projectName,
376                "",
377                CmsRole.WORKPLACE_USER.getGroupName(),
378                CmsRole.PROJECT_MANAGER.getGroupName(),
379                CmsProject.PROJECT_TYPE_TEMPORARY);
380        }
381        // make the project invisible for all users
382        tmpProject.setHidden(true);
383        // write the project to the database
384        adminCms.writeProject(tmpProject);
385        return tmpProject;
386    }
387
388    /**
389     * Checks whether the resources list contains any folders.<p>
390     *
391     * @return <code>true</code> if the resources list contains any folders
392     */
393    private boolean hasFolders() {
394
395        for (CmsResource resource : m_context.getResources()) {
396            if (resource.isFolder()) {
397                return true;
398            }
399        }
400        return false;
401    }
402
403    /**
404     * Initializes the form fields.<p>
405     *
406     * @return the form component
407     */
408    private FormLayout initForm() {
409
410        FormLayout form = new FormLayout();
411        form.setWidth("100%");
412        m_dateField = new CmsDateField();
413        m_dateField.setCaption(
414            CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_LABEL_DATE_PUBLISH_SCHEDULED_0));
415        form.addComponent(m_dateField);
416        m_includeSubResources = new CheckBox(
417            CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_PUBLISH_MULTI_SUBRESOURCES_0));
418        if (hasFolders()) {
419            form.addComponent(m_includeSubResources);
420        }
421
422        return form;
423    }
424}