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.commons.Messages; 053 054import java.text.DateFormat; 055import java.time.LocalDateTime; 056import java.util.Calendar; 057import java.util.Collections; 058import java.util.Date; 059import java.util.HashSet; 060import java.util.List; 061import java.util.Set; 062import java.util.SortedMap; 063import java.util.TreeMap; 064import java.util.stream.Collectors; 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 Admin CmsObject. */ 086 private static CmsObject m_adminCms; 087 088 /** The dialog context. */ 089 private I_CmsDialogContext m_context; 090 091 /** The date selection field. */ 092 private CmsDateField m_dateField; 093 094 /** The OK button. */ 095 private Button m_okButton; 096 097 /** The cancel button. */ 098 private Button m_cancelButton; 099 100 /** Include sub-resources check box. */ 101 private CheckBox m_includeSubResources; 102 103 /** 104 * Constructor.<p> 105 * 106 * @param context the dialog context 107 */ 108 public CmsPublishScheduledDialog(I_CmsDialogContext context) { 109 110 m_context = context; 111 displayResourceInfo(context.getResources()); 112 FormLayout form = initForm(); 113 setContent(form); 114 m_okButton = new Button(CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0)); 115 m_okButton.addClickListener(new ClickListener() { 116 117 private static final long serialVersionUID = 1L; 118 119 public void buttonClick(ClickEvent event) { 120 121 submit(); 122 } 123 }); 124 addButton(m_okButton); 125 m_cancelButton = new Button( 126 CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0)); 127 m_cancelButton.addClickListener(new ClickListener() { 128 129 private static final long serialVersionUID = 1L; 130 131 public void buttonClick(ClickEvent event) { 132 133 cancel(); 134 } 135 }); 136 addButton(m_cancelButton); 137 138 setActionHandler(new CmsOkCancelActionHandler() { 139 140 private static final long serialVersionUID = 1L; 141 142 @Override 143 protected void cancel() { 144 145 CmsPublishScheduledDialog.this.cancel(); 146 } 147 148 @Override 149 protected void ok() { 150 151 submit(); 152 } 153 }); 154 155 m_dateField.setRangeStart(LocalDateTime.now()); 156 157 } 158 159 /** 160 * Sets the admin cms object. This must happen during start up. 161 * @param cms the admin cms. 162 */ 163 public static void setAdminCms(CmsObject cms) { 164 165 m_adminCms = cms; 166 } 167 168 /** 169 * Cancels the dialog action.<p> 170 */ 171 void cancel() { 172 173 m_context.finish(Collections.<CmsUUID> emptyList()); 174 } 175 176 /** 177 * Submits the dialog action.<p> 178 */ 179 void submit() { 180 181 // if (!m_dateField.isValid()) { 182 // return; 183 // } 184 long current = System.currentTimeMillis(); 185 Date dateValue = m_dateField.getDate(); 186 long publishTime = dateValue.getTime(); 187 if (current > publishTime) { 188 m_context.error( 189 new CmsException(Messages.get().container(Messages.ERR_PUBLISH_SCHEDULED_DATE_IN_PAST_1, dateValue))); 190 } else { 191 try { 192 // get the user cms object 193 CmsObject cms = OpenCms.initCmsObject(m_context.getCms()); 194 195 // set the current user site to the admin cms object 196 CmsObject adminCms = getAdminCms(); 197 adminCms.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot()); 198 CmsProject tmpProject = createTempProject(adminCms, m_context.getResources(), dateValue); 199 // set project as current project 200 adminCms.getRequestContext().setCurrentProject(tmpProject); 201 cms.getRequestContext().setCurrentProject(tmpProject); 202 203 Set<CmsUUID> changeIds = new HashSet<CmsUUID>(); 204 Set<CmsResource> needToUnlockLater = new HashSet<>(); 205 for (CmsResource resource : m_context.getResources()) { 206 addToTempProject(adminCms, cms, resource, tmpProject, needToUnlockLater); 207 if (resource.isFolder() && m_includeSubResources.getValue().booleanValue()) { 208 List<CmsResource> subResources = cms.readResources( 209 resource, 210 CmsResourceFilter.ONLY_VISIBLE.addExcludeState(CmsResourceState.STATE_UNCHANGED), 211 true); 212 for (CmsResource sub : subResources) { 213 // check publish permissions on sub resource 214 if (cms.hasPermissions( 215 sub, 216 CmsPermissionSet.ACCESS_DIRECT_PUBLISH, 217 false, 218 CmsResourceFilter.ALL)) { 219 addToTempProject(adminCms, cms, sub, tmpProject, needToUnlockLater); 220 } 221 } 222 } 223 224 changeIds.add(resource.getStructureId()); 225 } 226 // create a new scheduled job 227 CmsScheduledJobInfo job = new CmsScheduledJobInfo(); 228 // the job name 229 String jobName = tmpProject.getName(); 230 jobName = jobName.replace("/", "/"); 231 // set the job parameters 232 job.setJobName(jobName); 233 job.setClassName("org.opencms.scheduler.jobs.CmsPublishScheduledJob"); 234 // create the cron expression 235 Calendar calendar = Calendar.getInstance(); 236 calendar.setTime(dateValue); 237 String cronExpr = "" 238 + calendar.get(Calendar.SECOND) 239 + " " 240 + calendar.get(Calendar.MINUTE) 241 + " " 242 + calendar.get(Calendar.HOUR_OF_DAY) 243 + " " 244 + calendar.get(Calendar.DAY_OF_MONTH) 245 + " " 246 + (calendar.get(Calendar.MONTH) + 1) 247 + " " 248 + "?" 249 + " " 250 + calendar.get(Calendar.YEAR); 251 // set the cron expression 252 job.setCronExpression(cronExpr); 253 // set the job active 254 job.setActive(true); 255 // create the context info 256 CmsContextInfo contextInfo = new CmsContextInfo(); 257 contextInfo.setProjectName(tmpProject.getName()); 258 contextInfo.setUserName(adminCms.getRequestContext().getCurrentUser().getName()); 259 // create the job schedule parameter 260 SortedMap<String, String> params = new TreeMap<String, String>(); 261 // the user to send mail to 262 String userName = m_context.getCms().getRequestContext().getCurrentUser().getName(); 263 params.put(CmsPublishScheduledJob.PARAM_USER, userName); 264 // the job name 265 params.put(CmsPublishScheduledJob.PARAM_JOBNAME, jobName); 266 // the link check 267 params.put(CmsPublishScheduledJob.PARAM_LINKCHECK, "true"); 268 String unlockIdList = needToUnlockLater.stream().map(res -> res.getStructureId().toString()).collect( 269 Collectors.joining(",")); 270 params.put(CmsPublishScheduledJob.PARAM_UNLOCK_LIST, unlockIdList); 271 // add the job schedule parameter 272 job.setParameters(params); 273 // add the context info to the scheduled job 274 job.setContextInfo(contextInfo); 275 // add the job to the scheduled job list 276 OpenCms.getScheduleManager().scheduleJob(adminCms, job); 277 // update the XML configuration 278 OpenCms.writeConfiguration(CmsSchedulerConfiguration.class); 279 m_context.finish(changeIds); 280 } catch (CmsException ex) { 281 LOG.error("Error performing publish scheduled dialog operation.", ex); 282 m_context.error(ex); 283 } 284 } 285 286 } 287 288 /** 289 * Adds the given resource to the temporary project.<p> 290 * 291 * @param adminCms the admin cms context 292 * @param userCms the user cms context 293 * @param resource the resource 294 * @param tmpProject the temporary project 295 * @param needToUnlockLater a set to which this method adds resources that should be explicitly unlocked later 296 * 297 * @throws CmsException in case something goes wrong 298 */ 299 private void addToTempProject( 300 CmsObject adminCms, 301 CmsObject userCms, 302 CmsResource resource, 303 CmsProject tmpProject, 304 Set<CmsResource> needToUnlockLater) 305 throws CmsException { 306 307 // copy the resource to the project 308 adminCms.copyResourceToProject(resource); 309 310 // lock the resource in the current project 311 CmsLock lock = userCms.getLock(resource); 312 // prove is current lock from current but not in current project 313 if ((lock != null) 314 && lock.isOwnedBy(userCms.getRequestContext().getCurrentUser()) 315 && !lock.isOwnedInProjectBy( 316 userCms.getRequestContext().getCurrentUser(), 317 userCms.getRequestContext().getCurrentProject())) { 318 // file is locked by current user but not in current project 319 // change the lock from this file 320 userCms.changeLock(resource); 321 } 322 // lock resource from current user in current project 323 userCms.lockResource(resource); 324 if (resource.getState().isUnchanged()) { 325 needToUnlockLater.add(resource); 326 } 327 // get current lock 328 lock = userCms.getLock(resource); 329 } 330 331 /** 332 * Creates the publish project's name for a given root path and publish date.<p> 333 * 334 * @param rootPath the publish resource's root path 335 * @param date the publish date 336 * 337 * @return the publish project name 338 */ 339 private String computeProjectName(String rootPath, Date date) { 340 341 // create the temporary project, which is deleted after publishing 342 // the publish scheduled date in project name 343 String dateTime = CmsDateUtil.getDateTime(date, DateFormat.SHORT, getLocale()); 344 // the resource name to publish scheduled 345 String projectName = CmsVaadinUtils.getMessageText( 346 Messages.GUI_PUBLISH_SCHEDULED_PROJECT_NAME_2, 347 rootPath, 348 dateTime); 349 // the HTML encoding for slashes is necessary because of the slashes in english date time format 350 // in project names slahes are not allowed, because these are separators for organizaional units 351 projectName = projectName.replace("/", "/"); 352 while (projectName.length() > 190) { 353 rootPath = "..." + rootPath.substring(5, rootPath.length()); 354 projectName = computeProjectName(rootPath, date); 355 } 356 return projectName; 357 } 358 359 /** 360 * Creates the temporary project.<p> 361 * 362 * @param adminCms the admin cms context 363 * @param resources the resources 364 * @param date the publish date 365 * 366 * @return the project 367 * 368 * @throws CmsException in case writing the project fails 369 */ 370 private CmsProject createTempProject(CmsObject adminCms, List<CmsResource> resources, Date date) 371 throws CmsException { 372 373 CmsProject tmpProject; 374 375 String rootPath = resources.get(0).getRootPath(); 376 if (resources.size() > 1) { 377 rootPath = CmsResource.getParentFolder(rootPath); 378 } 379 String projectName = computeProjectName(rootPath, date); 380 381 try { 382 // create the project 383 tmpProject = adminCms.createProject( 384 projectName, 385 "", 386 CmsRole.WORKPLACE_USER.getGroupName(), 387 CmsRole.PROJECT_MANAGER.getGroupName(), 388 CmsProject.PROJECT_TYPE_TEMPORARY); 389 } catch (CmsException e) { 390 String resName = CmsResource.getName(rootPath); 391 if (resName.length() > 64) { 392 resName = resName.substring(0, 64) + "..."; 393 } 394 // use UUID to make sure the project name is still unique 395 projectName = computeProjectName(resName, date) + " [" + new CmsUUID() + "]"; 396 // create the project 397 tmpProject = adminCms.createProject( 398 projectName, 399 "", 400 CmsRole.WORKPLACE_USER.getGroupName(), 401 CmsRole.PROJECT_MANAGER.getGroupName(), 402 CmsProject.PROJECT_TYPE_TEMPORARY); 403 } 404 // make the project invisible for all users 405 tmpProject.setHidden(true); 406 // write the project to the database 407 adminCms.writeProject(tmpProject); 408 return tmpProject; 409 } 410 411 /** 412 * Returns a copy of the admin cms object. 413 * @return a copy of the admin cms object. 414 * @throws CmsException thrown if copying the cms object fails. 415 */ 416 private CmsObject getAdminCms() throws CmsException { 417 418 return OpenCms.initCmsObject(m_adminCms); 419 } 420 421 /** 422 * Checks whether the resources list contains any folders.<p> 423 * 424 * @return <code>true</code> if the resources list contains any folders 425 */ 426 private boolean hasFolders() { 427 428 for (CmsResource resource : m_context.getResources()) { 429 if (resource.isFolder()) { 430 return true; 431 } 432 } 433 return false; 434 } 435 436 /** 437 * Initializes the form fields.<p> 438 * 439 * @return the form component 440 */ 441 private FormLayout initForm() { 442 443 FormLayout form = new FormLayout(); 444 form.setWidth("100%"); 445 m_dateField = new CmsDateField(); 446 m_dateField.setCaption( 447 CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_LABEL_DATE_PUBLISH_SCHEDULED_0)); 448 form.addComponent(m_dateField); 449 m_includeSubResources = new CheckBox( 450 CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_PUBLISH_MULTI_SUBRESOURCES_0)); 451 if (hasFolders()) { 452 form.addComponent(m_includeSubResources); 453 } 454 455 return form; 456 } 457}