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.workplace.editors.directedit; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsResourceTypeConfig; 032import org.opencms.ade.contenteditor.shared.CmsEditorConstants; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.CmsVfsResourceNotFoundException; 036import org.opencms.file.types.CmsResourceTypeBinary; 037import org.opencms.file.types.CmsResourceTypeImage; 038import org.opencms.file.types.CmsResourceTypePlain; 039import org.opencms.file.types.CmsResourceTypeXmlContent; 040import org.opencms.file.types.CmsResourceTypeXmlPage; 041import org.opencms.file.types.I_CmsResourceType; 042import org.opencms.gwt.shared.CmsGwtConstants; 043import org.opencms.gwt.shared.I_CmsAutoBeanFactory; 044import org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo; 045import org.opencms.gwt.shared.I_CmsEditableDataExtensions; 046import org.opencms.i18n.CmsEncoder; 047import org.opencms.i18n.CmsMessages; 048import org.opencms.json.JSONException; 049import org.opencms.json.JSONObject; 050import org.opencms.jsp.util.CmsJspStandardContextBean; 051import org.opencms.lock.CmsLock; 052import org.opencms.main.CmsException; 053import org.opencms.main.CmsLog; 054import org.opencms.main.OpenCms; 055import org.opencms.security.CmsPermissionSet; 056import org.opencms.security.CmsPermissionViolationException; 057import org.opencms.util.CmsStringUtil; 058import org.opencms.util.CmsUUID; 059import org.opencms.workplace.editors.Messages; 060import org.opencms.xml.containerpage.CmsContainerElementBean; 061 062import java.util.Arrays; 063import java.util.List; 064import java.util.Locale; 065import java.util.Random; 066 067import javax.servlet.jsp.JspException; 068import javax.servlet.jsp.PageContext; 069 070import org.apache.commons.logging.Log; 071 072import com.google.web.bindery.autobean.shared.AutoBean; 073import com.google.web.bindery.autobean.shared.AutoBeanCodex; 074import com.google.web.bindery.autobean.vm.AutoBeanFactorySource; 075 076/** 077 * Provider for the OpenCms AdvancedDirectEdit.<p> 078 * 079 * Since OpenCms version 8.0.0.<p> 080 * 081 * This provider DOES NOT support {@link CmsDirectEditMode#MANUAL} mode.<p> 082 * 083 * @since 8.0.0 084 */ 085public class CmsAdvancedDirectEditProvider extends A_CmsDirectEditProvider { 086 087 /** 088 * Direct edit permissions according to the sitemap configuration. 089 */ 090 public static enum SitemapDirectEditPermissions { 091 092 /** Everything allowed. */ 093 all(true, true, true), 094 095 /** Not found in sitemap config. */ 096 editAndCreate(true, true, false), 097 098 /** Can edit, but not add. */ 099 editOnly(false, true, true), 100 101 /** Nothing allowed. */ 102 none(false, false, false); 103 104 /** True if creating elements is allowed. */ 105 private boolean m_create; 106 107 /** True if editing elements is allowed. */ 108 private boolean m_edit; 109 110 /** Allow favoriting. */ 111 private boolean m_favorite; 112 113 /** 114 * Private constructor. 115 * 116 * @param create true if creation is allowed 117 * @param edit true if editing is allowed 118 * @param favorite true if favoriting is allowed 119 */ 120 SitemapDirectEditPermissions(boolean create, boolean edit, boolean favorite) { 121 122 m_create = create; 123 m_edit = edit; 124 m_favorite = favorite; 125 } 126 127 /** 128 * Returns true if element creation is allowed. 129 * 130 * @return true if element creation is allowed 131 */ 132 public boolean canCreate() { 133 134 return m_create; 135 } 136 137 /** 138 * Returns true if editing elements is allowed. 139 * 140 * @return true if editing elements is allowed 141 */ 142 public boolean canEdit() { 143 144 return m_edit; 145 } 146 147 /** 148 * Return true if favoriting is allowed. 149 * 150 * @return true if favoriting is allowed 151 */ 152 public boolean canFavorite() { 153 154 return m_favorite; 155 } 156 } 157 158 /** The log object for this class. */ 159 private static final Log LOG = CmsLog.getLog(CmsAdvancedDirectEditProvider.class); 160 161 /** Indicates the permissions for the last element the was opened. */ 162 protected int m_lastPermissionMode; 163 164 /** True if the elements should be assigned randomly generated ids. */ 165 protected boolean m_useIds; 166 167 /** Factory for the editable data extended attributes. */ 168 I_CmsAutoBeanFactory m_editableDataExtensionsFactory = AutoBeanFactorySource.create(I_CmsAutoBeanFactory.class); 169 170 /** The random number generator used for element ids. */ 171 private Random m_random = new Random(); 172 173 /** 174 * Returns the end HTML for a disabled direct edit button.<p> 175 * 176 * @return the end HTML for a disabled direct edit button 177 */ 178 public String endDirectEditDisabled() { 179 180 return ""; 181 } 182 183 /** 184 * Returns the end HTML for an enabled direct edit button.<p> 185 * 186 * @return the end HTML for an enabled direct edit button 187 */ 188 public String endDirectEditEnabled() { 189 190 String tag = CmsGwtConstants.CLASS_EDITABLE_END; 191 192 return "<" + tag + " class=\"" + CmsGwtConstants.CLASS_EDITABLE_END + "\"></" + tag + ">\n"; 193 } 194 195 /** 196 * Generates a random element id.<p> 197 * 198 * @return a random element id 199 */ 200 public synchronized String getRandomId() { 201 202 return "editable_" + Math.abs(m_random.nextLong()); 203 } 204 205 /** 206 * @see org.opencms.workplace.editors.directedit.A_CmsDirectEditProvider#getResourceInfo(java.lang.String) 207 * 208 * Similar to the method in the superclass, but removes the write permission check, as this is handled differently. 209 */ 210 @Override 211 public CmsDirectEditResourceInfo getResourceInfo(CmsDirectEditParams params, String resourceName) { 212 213 try { 214 // first check some simple preconditions for direct edit 215 if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) { 216 // don't show direct edit button in online project 217 return CmsDirectEditResourceInfo.INACTIVE; 218 } 219 if (CmsResource.isTemporaryFileName(resourceName)) { 220 // don't show direct edit button on a temporary file 221 return CmsDirectEditResourceInfo.INACTIVE; 222 } 223 if (!m_cms.isInsideCurrentProject(resourceName)) { 224 // don't show direct edit button on files not belonging to the current project 225 return CmsDirectEditResourceInfo.INACTIVE; 226 } 227 // read the target resource 228 CmsResource resource = m_cms.readResource(resourceName, CmsResourceFilter.ALL); 229 if (!OpenCms.getResourceManager().getResourceType(resource.getTypeId()).isDirectEditable() 230 && !resource.isFolder()) { 231 if (CmsStringUtil.isEmptyOrWhitespaceOnly(params.getUploadFolder())) { 232 return CmsDirectEditResourceInfo.INACTIVE; 233 } 234 } 235 // check the resource lock 236 CmsLock lock = m_cms.getLock(resource); 237 boolean locked = !(lock.isUnlocked() 238 || lock.isOwnedInProjectBy( 239 m_cms.getRequestContext().getCurrentUser(), 240 m_cms.getRequestContext().getCurrentProject())); 241 // check the users permissions on the resource 242 // only if write permissions are granted the resource may be direct editable 243 if (locked) { 244 // a locked resource must be shown as "disabled" 245 return new CmsDirectEditResourceInfo(CmsDirectEditPermissions.DISABLED, resource, lock); 246 } 247 // if we have write permission and the resource is not locked then direct edit is enabled 248 return new CmsDirectEditResourceInfo(CmsDirectEditPermissions.ENABLED, resource, lock); 249 250 } catch (Exception e) { 251 // all exceptions: don't mix up the result HTML, always return INACTIVE mode 252 if (LOG.isWarnEnabled()) { 253 LOG.warn( 254 org.opencms.workplace.editors.Messages.get().getBundle().key( 255 org.opencms.workplace.editors.Messages.LOG_CALC_EDIT_MODE_FAILED_1, 256 resourceName), 257 e); 258 } 259 } 260 // otherwise the resource is not direct editable 261 return CmsDirectEditResourceInfo.INACTIVE; 262 } 263 264 /** 265 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#insertDirectEditEnd(javax.servlet.jsp.PageContext) 266 */ 267 public void insertDirectEditEnd(PageContext context) throws JspException { 268 269 String content; 270 switch (m_lastPermissionMode) { 271 272 case 1: // disabled 273 // content = endDirectEditDisabled(); 274 // break; 275 case 2: // enabled 276 content = endDirectEditEnabled(); 277 break; 278 default: // inactive or undefined 279 content = null; 280 } 281 m_lastPermissionMode = 0; 282 print(context, content); 283 } 284 285 /** 286 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#insertDirectEditIncludes(javax.servlet.jsp.PageContext, org.opencms.workplace.editors.directedit.CmsDirectEditParams) 287 */ 288 @SuppressWarnings("unused") 289 public void insertDirectEditIncludes(PageContext context, CmsDirectEditParams params) throws JspException { 290 291 // For Advanced Direct Edit all necessary js and css-code is included by the enableADE tag. Further includes in the head are not needed. 292 293 } 294 295 /** 296 * @see org.opencms.workplace.editors.directedit.A_CmsDirectEditProvider#insertDirectEditListMetadata(javax.servlet.jsp.PageContext, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 297 */ 298 @Override 299 public void insertDirectEditListMetadata(PageContext context, I_CmsContentLoadCollectorInfo info) 300 throws JspException { 301 302 if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) { 303 // the metadata is only needed for editing 304 return; 305 } 306 I_CmsAutoBeanFactory collectorInfoFactory = AutoBeanFactorySource.create(I_CmsAutoBeanFactory.class); 307 AutoBean<I_CmsContentLoadCollectorInfo> collectorInfoAutoBean = collectorInfoFactory.wrapCollectorInfo(info); 308 String serializedCollectorInfo = AutoBeanCodex.encode(collectorInfoAutoBean).getPayload(); 309 310 String tag = CmsGwtConstants.CLASS_COLLECTOR_INFO; 311 String marker = "<" 312 + tag 313 + " class='" 314 + CmsGwtConstants.CLASS_COLLECTOR_INFO 315 + "' style='display: none !important;' " 316 + CmsGwtConstants.ATTR_DATA_COLLECTOR 317 + "='" 318 + CmsEncoder.escapeXml(serializedCollectorInfo) 319 + "'></" 320 + tag 321 + ">"; 322 print(context, marker); 323 } 324 325 /** 326 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#insertDirectEditStart(javax.servlet.jsp.PageContext, org.opencms.workplace.editors.directedit.CmsDirectEditParams) 327 */ 328 public boolean insertDirectEditStart(PageContext context, CmsDirectEditParams params) throws JspException { 329 330 String content; 331 // check the direct edit permissions of the current user 332 CmsDirectEditResourceInfo resourceInfo = getResourceInfo(params, params.getResourceName()); 333 334 // check the permission mode 335 m_lastPermissionMode = resourceInfo.getPermissions().getPermission(); 336 switch (m_lastPermissionMode) { 337 case 1: // disabled 338 // content = startDirectEditDisabled(params, resourceInfo); 339 // break; 340 case 2: // enabled 341 try { 342 CmsJspStandardContextBean contextBean = CmsJspStandardContextBean.getInstance(context.getRequest()); 343 CmsContainerElementBean element = contextBean.getElement(); 344 if ((element != null) && element.getId().equals(resourceInfo.getResource().getStructureId())) { 345 params.m_element = element.editorHash(); 346 params.setContainerElement(element); 347 } 348 content = startDirectEditEnabled(params, resourceInfo); 349 } catch (JSONException e) { 350 throw new JspException(e); 351 } 352 break; 353 default: // inactive or undefined 354 content = null; 355 } 356 print(context, content); 357 return content != null; 358 } 359 360 /** 361 * Returns <code>false</code> because the default provider does not support manual button placement.<p> 362 * 363 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#isManual(org.opencms.workplace.editors.directedit.CmsDirectEditMode) 364 */ 365 @Override 366 public boolean isManual(CmsDirectEditMode mode) { 367 368 return false; 369 } 370 371 /** 372 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#newInstance() 373 */ 374 public I_CmsDirectEditProvider newInstance() { 375 376 CmsAdvancedDirectEditProvider result = new CmsAdvancedDirectEditProvider(); 377 result.m_configurationParameters = m_configurationParameters; 378 return result; 379 } 380 381 /** 382 * Returns the start HTML for a disabled direct edit button.<p> 383 * 384 * @param params the direct edit parameters 385 * @param resourceInfo contains information about the resource to edit 386 * 387 * @return the start HTML for a disabled direct edit button 388 */ 389 public String startDirectEditDisabled(CmsDirectEditParams params, CmsDirectEditResourceInfo resourceInfo) { 390 391 StringBuffer result = new StringBuffer(256); 392 393 result.append("<!-- EDIT BLOCK START (DISABLED): "); 394 result.append(params.m_resourceName); 395 result.append(" ["); 396 result.append(resourceInfo.getResource().getState()); 397 result.append("] "); 398 if (!resourceInfo.getLock().isUnlocked()) { 399 result.append(" locked "); 400 result.append(resourceInfo.getLock().getProject().getName()); 401 } 402 result.append(" -->\n"); 403 return result.toString(); 404 } 405 406 /** 407 * Returns the start HTML for an enabled direct edit button.<p> 408 * 409 * @param params the direct edit parameters 410 * @param resourceInfo contains information about the resource to edit 411 * 412 * @return the start HTML for an enabled direct edit button 413 * @throws JSONException if a JSON handling error occurs 414 */ 415 public String startDirectEditEnabled(CmsDirectEditParams params, CmsDirectEditResourceInfo resourceInfo) 416 throws JSONException { 417 418 String editLocale = m_cms.getRequestContext().getLocale().toString(); 419 String editId = getNextDirectEditId(); 420 String editNewLink = CmsEncoder.encode(params.getLinkForNew()); 421 // putting together all needed data 422 JSONObject editableData = new JSONObject(); 423 CmsResource resource = resourceInfo.getResource(); 424 boolean writable = false; 425 String uri = m_cms.getRequestContext().getUri(); 426 uri = m_cms.getRequestContext().addSiteRoot(uri); 427 CmsADEConfigData configData = OpenCms.getADEManager().lookupConfigurationWithCache(m_cms, uri); 428 if (resource != null) { 429 try { 430 writable = m_cms.hasPermissions( 431 resource, 432 CmsPermissionSet.ACCESS_WRITE, 433 false, 434 CmsResourceFilter.IGNORE_EXPIRATION); 435 } catch (CmsException e) { 436 LOG.error(e.getLocalizedMessage(), e); 437 } 438 } 439 editableData.put("editId", editId); 440 CmsContainerElementBean containerElement = params.getContainerElement(); 441 if (containerElement != null) { 442 editableData.put(CmsGwtConstants.ATTR_ELEMENT_ID, containerElement.editorHash()); 443 } 444 445 editableData.put("structureId", resourceInfo.getResource().getStructureId()); 446 editableData.put("sitePath", params.getResourceName()); 447 editableData.put("elementlanguage", editLocale); 448 editableData.put("elementname", params.getElement()); 449 editableData.put("newlink", editNewLink); 450 editableData.put("hasResource", resource != null); 451 editableData.put("newtitle", m_messages.key(Messages.GUI_EDITOR_TITLE_NEW_0)); 452 editableData.put( 453 "unreleaseOrExpired", 454 !resourceInfo.getResource().isReleasedAndNotExpired(System.currentTimeMillis())); 455 if (params.getId() != null) { 456 editableData.put(CmsEditorConstants.ATTR_CONTEXT_ID, params.getId().toString()); 457 } 458 editableData.put(CmsEditorConstants.ATTR_POST_CREATE_HANDLER, params.getPostCreateHandler()); 459 CmsUUID viewId = CmsUUID.getNullUUID(); 460 boolean hasEditHandler = false; 461 String typeName = null; 462 if ((resourceInfo.getResource() != null) && resourceInfo.getResource().isFile()) { 463 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resourceInfo.getResource()); 464 if (type instanceof CmsResourceTypeXmlContent) { 465 hasEditHandler = ((CmsResourceTypeXmlContent)type).getEditHandler(m_cms) != null; 466 } 467 typeName = OpenCms.getResourceManager().getResourceType(resourceInfo.getResource()).getTypeName(); 468 CmsResourceTypeConfig typeConfig = configData.getResourceType(typeName); 469 if (typeConfig != null) { 470 viewId = typeConfig.getElementView(); 471 } 472 } else { 473 String linkForNew = params.getLinkForNew(); 474 if (linkForNew != null) { 475 String[] components = linkForNew.split("\\|"); 476 typeName = components[components.length - 1]; 477 CmsResourceTypeConfig typeConfig = configData.getResourceType(typeName); 478 if (typeConfig != null) { 479 viewId = typeConfig.getElementView(); 480 } 481 } 482 } 483 SitemapDirectEditPermissions sitemapConfigPermissions = configData.getDirectEditPermissions(typeName); 484 boolean hasNew = false; 485 editableData.put( 486 "hasEdit", 487 params.getButtonSelection().isShowEdit() 488 && (CmsResourceTypeXmlContent.isXmlContent(resource) || CmsResourceTypeXmlPage.isXmlPage(resource))); 489 editableData.put( 490 "hasDelete", 491 params.getButtonSelection().isShowDelete() && writable && sitemapConfigPermissions.canEdit()); 492 // hasNew = params.getButtonSelection().isShowNew() && writable && sitemapConfigPermissions.canEdit(); 493 494 editableData.put(CmsEditorConstants.ATTR_ELEMENT_VIEW, viewId); 495 editableData.put("hasEditHandler", hasEditHandler); 496 boolean favorites = sitemapConfigPermissions.canFavorite(); 497 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms); 498 CmsMessages messages = Messages.get().getBundle(locale); 499 if ((m_lastPermissionMode == 1) || !writable || (!sitemapConfigPermissions.canEdit())) { 500 String noEditReason = null; 501 if (sitemapConfigPermissions.canCreate()) { 502 noEditReason = messages.key(Messages.GUI_DIRECTEDIT_CAN_ONLY_BE_CREATED_0);// "Can only be created but not edited"; 503 } else { 504 noEditReason = messages.key(Messages.GUI_DIRECTEDIT_CANNOT_BE_CREATED_OR_EDITED_0); // "Cannot be created or edited"; 505 } 506 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(noEditReason)) { 507 editableData.put("noEditReason", noEditReason); 508 } 509 } 510 editableData.put(CmsGwtConstants.ATTR_FAVORITE, Boolean.valueOf(favorites)); 511 AutoBean<I_CmsEditableDataExtensions> extensions = m_editableDataExtensionsFactory.createExtensions(); 512 boolean isUploadType = false; 513 List<String> uploadTypes = Arrays.asList( 514 CmsResourceTypeBinary.getStaticTypeName(), 515 CmsResourceTypeImage.getStaticTypeName(), 516 CmsResourceTypePlain.getStaticTypeName()); 517 if ((resource != null) && !resource.isFolder()) { 518 for (String type : uploadTypes) { 519 if (OpenCms.getResourceManager().matchResourceType(type, resource.getTypeId())) { 520 isUploadType = true; 521 break; 522 } 523 } 524 } else { 525 String newLink = params.getLinkForNew(); 526 if (newLink != null) { 527 int pipePos = newLink.lastIndexOf('|'); 528 String typePart = newLink.substring(pipePos + 1); 529 isUploadType = uploadTypes.contains(typePart); 530 } 531 } 532 String uploadFolder = params.getUploadFolder(); 533 hasNew = params.getButtonSelection().isShowNew(); 534 if (isUploadType) { 535 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(uploadFolder) && !"none".equals(uploadFolder)) { 536 CmsResource uploadFolderResource = null; 537 try { 538 uploadFolderResource = m_cms.readResource(uploadFolder, CmsResourceFilter.IGNORE_EXPIRATION); 539 hasNew &= m_cms.hasPermissions( 540 uploadFolderResource, 541 CmsPermissionSet.ACCESS_WRITE, 542 false, 543 CmsResourceFilter.IGNORE_EXPIRATION); 544 } catch (CmsVfsResourceNotFoundException e) { 545 LOG.debug(e.getLocalizedMessage(), e); 546 } catch (CmsPermissionViolationException e) { 547 LOG.debug( 548 "hasNew = false for upload folder " + uploadFolder + ", reason: " + e.getLocalizedMessage(), 549 e); 550 hasNew = false; 551 } catch (CmsException e) { 552 LOG.error(e.getLocalizedMessage(), e); 553 hasNew = false; 554 } 555 } else { 556 hasNew = false; 557 } 558 } else { 559 hasNew &= sitemapConfigPermissions.canCreate(); 560 } 561 extensions.as().setUploadEnabled(isUploadType && hasUploadSupport() && (params.getUploadFolder() != null)); 562 extensions.as().setUploadFolder(params.getUploadFolder()); 563 editableData.put(CmsGwtConstants.ATTR_EXTENSIONS, AutoBeanCodex.encode(extensions).getPayload()); 564 editableData.put("hasNew", hasNew); 565 566 StringBuffer result = new StringBuffer(512); 567 if (m_useIds) { 568 String tag = CmsGwtConstants.CLASS_EDITABLE; 569 result.append( 570 "<" 571 + tag 572 + " id=\"" // use custom tag 573 + getRandomId() 574 + "\" class='" 575 + CmsGwtConstants.CLASS_EDITABLE 576 + "' " 577 + CmsGwtConstants.ATTR_DATA_EDITABLE 578 + "='").append(editableData.toString()).append("'></" + tag + ">\n"); 579 } else { 580 String tag = CmsGwtConstants.CLASS_EDITABLE; 581 result.append( 582 "<" 583 + tag 584 + " class='" 585 + CmsGwtConstants.CLASS_EDITABLE 586 + "' " 587 + CmsGwtConstants.ATTR_DATA_EDITABLE 588 + "='").append(editableData.toString()).append("'></" + tag + ">\n"); 589 } 590 return result.toString(); 591 } 592 593 /** 594 * Checks if upload for binaries are supported by this provider. 595 * 596 * @return true if binary upload is supported 597 */ 598 protected boolean hasUploadSupport() { 599 600 return true; 601 } 602 603}