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.I_CmsResourceType; 041import org.opencms.gwt.shared.CmsGwtConstants; 042import org.opencms.gwt.shared.I_CmsCollectorInfoFactory; 043import org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo; 044import org.opencms.gwt.shared.I_CmsEditableDataExtensions; 045import org.opencms.gwt.shared.I_CmsEditableDataExtensionsFactory; 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_CmsEditableDataExtensionsFactory m_editableDataExtensionsFactory = AutoBeanFactorySource.create( 169 I_CmsEditableDataExtensionsFactory.class); 170 171 /** The random number generator used for element ids. */ 172 private Random m_random = new Random(); 173 174 /** 175 * Returns the end HTML for a disabled direct edit button.<p> 176 * 177 * @return the end HTML for a disabled direct edit button 178 */ 179 public String endDirectEditDisabled() { 180 181 return ""; 182 } 183 184 /** 185 * Returns the end HTML for an enabled direct edit button.<p> 186 * 187 * @return the end HTML for an enabled direct edit button 188 */ 189 public String endDirectEditEnabled() { 190 191 return "<div class=\"" + CmsGwtConstants.CLASS_EDITABLE_END + "\"></div>\n"; 192 } 193 194 /** 195 * Generates a random element id.<p> 196 * 197 * @return a random element id 198 */ 199 public synchronized String getRandomId() { 200 201 return "editable_" + Math.abs(m_random.nextLong()); 202 } 203 204 /** 205 * @see org.opencms.workplace.editors.directedit.A_CmsDirectEditProvider#getResourceInfo(java.lang.String) 206 * 207 * Similar to the method in the superclass, but removes the write permission check, as this is handled differently. 208 */ 209 @Override 210 public CmsDirectEditResourceInfo getResourceInfo(CmsDirectEditParams params, String resourceName) { 211 212 try { 213 // first check some simple preconditions for direct edit 214 if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) { 215 // don't show direct edit button in online project 216 return CmsDirectEditResourceInfo.INACTIVE; 217 } 218 if (CmsResource.isTemporaryFileName(resourceName)) { 219 // don't show direct edit button on a temporary file 220 return CmsDirectEditResourceInfo.INACTIVE; 221 } 222 if (!m_cms.isInsideCurrentProject(resourceName)) { 223 // don't show direct edit button on files not belonging to the current project 224 return CmsDirectEditResourceInfo.INACTIVE; 225 } 226 // read the target resource 227 CmsResource resource = m_cms.readResource(resourceName, CmsResourceFilter.ALL); 228 if (!OpenCms.getResourceManager().getResourceType(resource.getTypeId()).isDirectEditable() 229 && !resource.isFolder()) { 230 if (CmsStringUtil.isEmptyOrWhitespaceOnly(params.getUploadFolder())) { 231 return CmsDirectEditResourceInfo.INACTIVE; 232 } 233 } 234 // check the resource lock 235 CmsLock lock = m_cms.getLock(resource); 236 boolean locked = !(lock.isUnlocked() 237 || lock.isOwnedInProjectBy( 238 m_cms.getRequestContext().getCurrentUser(), 239 m_cms.getRequestContext().getCurrentProject())); 240 // check the users permissions on the resource 241 // only if write permissions are granted the resource may be direct editable 242 if (locked) { 243 // a locked resource must be shown as "disabled" 244 return new CmsDirectEditResourceInfo(CmsDirectEditPermissions.DISABLED, resource, lock); 245 } 246 // if we have write permission and the resource is not locked then direct edit is enabled 247 return new CmsDirectEditResourceInfo(CmsDirectEditPermissions.ENABLED, resource, lock); 248 249 } catch (Exception e) { 250 // all exceptions: don't mix up the result HTML, always return INACTIVE mode 251 if (LOG.isWarnEnabled()) { 252 LOG.warn( 253 org.opencms.workplace.editors.Messages.get().getBundle().key( 254 org.opencms.workplace.editors.Messages.LOG_CALC_EDIT_MODE_FAILED_1, 255 resourceName), 256 e); 257 } 258 } 259 // otherwise the resource is not direct editable 260 return CmsDirectEditResourceInfo.INACTIVE; 261 } 262 263 /** 264 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#insertDirectEditEnd(javax.servlet.jsp.PageContext) 265 */ 266 public void insertDirectEditEnd(PageContext context) throws JspException { 267 268 String content; 269 switch (m_lastPermissionMode) { 270 271 case 1: // disabled 272 // content = endDirectEditDisabled(); 273 // break; 274 case 2: // enabled 275 content = endDirectEditEnabled(); 276 break; 277 default: // inactive or undefined 278 content = null; 279 } 280 m_lastPermissionMode = 0; 281 print(context, content); 282 } 283 284 /** 285 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#insertDirectEditIncludes(javax.servlet.jsp.PageContext, org.opencms.workplace.editors.directedit.CmsDirectEditParams) 286 */ 287 @SuppressWarnings("unused") 288 public void insertDirectEditIncludes(PageContext context, CmsDirectEditParams params) throws JspException { 289 290 // For Advanced Direct Edit all necessary js and css-code is included by the enableADE tag. Further includes in the head are not needed. 291 292 } 293 294 /** 295 * @see org.opencms.workplace.editors.directedit.A_CmsDirectEditProvider#insertDirectEditListMetadata(javax.servlet.jsp.PageContext, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 296 */ 297 @Override 298 public void insertDirectEditListMetadata(PageContext context, I_CmsContentLoadCollectorInfo info) 299 throws JspException { 300 301 if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) { 302 // the metadata is only needed for editing 303 return; 304 } 305 I_CmsCollectorInfoFactory collectorInfoFactory = AutoBeanFactorySource.create(I_CmsCollectorInfoFactory.class); 306 AutoBean<I_CmsContentLoadCollectorInfo> collectorInfoAutoBean = collectorInfoFactory.wrapCollectorInfo(info); 307 String serializedCollectorInfo = AutoBeanCodex.encode(collectorInfoAutoBean).getPayload(); 308 309 String marker = "<div class='" 310 + CmsGwtConstants.CLASS_COLLECTOR_INFO 311 + "' style='display: none !important;' " 312 + CmsGwtConstants.ATTR_DATA_COLLECTOR 313 + "='" 314 + CmsEncoder.escapeXml(serializedCollectorInfo) 315 + "'></div>"; 316 print(context, marker); 317 } 318 319 /** 320 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#insertDirectEditStart(javax.servlet.jsp.PageContext, org.opencms.workplace.editors.directedit.CmsDirectEditParams) 321 */ 322 public boolean insertDirectEditStart(PageContext context, CmsDirectEditParams params) throws JspException { 323 324 String content; 325 // check the direct edit permissions of the current user 326 CmsDirectEditResourceInfo resourceInfo = getResourceInfo(params, params.getResourceName()); 327 328 // check the permission mode 329 m_lastPermissionMode = resourceInfo.getPermissions().getPermission(); 330 switch (m_lastPermissionMode) { 331 case 1: // disabled 332 // content = startDirectEditDisabled(params, resourceInfo); 333 // break; 334 case 2: // enabled 335 try { 336 CmsJspStandardContextBean contextBean = CmsJspStandardContextBean.getInstance(context.getRequest()); 337 CmsContainerElementBean element = contextBean.getElement(); 338 if ((element != null) && element.getId().equals(resourceInfo.getResource().getStructureId())) { 339 params.m_element = element.editorHash(); 340 params.setContainerElement(element); 341 } 342 content = startDirectEditEnabled(params, resourceInfo); 343 } catch (JSONException e) { 344 throw new JspException(e); 345 } 346 break; 347 default: // inactive or undefined 348 content = null; 349 } 350 print(context, content); 351 return content != null; 352 } 353 354 /** 355 * Returns <code>false</code> because the default provider does not support manual button placement.<p> 356 * 357 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#isManual(org.opencms.workplace.editors.directedit.CmsDirectEditMode) 358 */ 359 @Override 360 public boolean isManual(CmsDirectEditMode mode) { 361 362 return false; 363 } 364 365 /** 366 * @see org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider#newInstance() 367 */ 368 public I_CmsDirectEditProvider newInstance() { 369 370 CmsAdvancedDirectEditProvider result = new CmsAdvancedDirectEditProvider(); 371 result.m_configurationParameters = m_configurationParameters; 372 return result; 373 } 374 375 /** 376 * Returns the start HTML for a disabled direct edit button.<p> 377 * 378 * @param params the direct edit parameters 379 * @param resourceInfo contains information about the resource to edit 380 * 381 * @return the start HTML for a disabled direct edit button 382 */ 383 public String startDirectEditDisabled(CmsDirectEditParams params, CmsDirectEditResourceInfo resourceInfo) { 384 385 StringBuffer result = new StringBuffer(256); 386 387 result.append("<!-- EDIT BLOCK START (DISABLED): "); 388 result.append(params.m_resourceName); 389 result.append(" ["); 390 result.append(resourceInfo.getResource().getState()); 391 result.append("] "); 392 if (!resourceInfo.getLock().isUnlocked()) { 393 result.append(" locked "); 394 result.append(resourceInfo.getLock().getProject().getName()); 395 } 396 result.append(" -->\n"); 397 return result.toString(); 398 } 399 400 /** 401 * Returns the start HTML for an enabled direct edit button.<p> 402 * 403 * @param params the direct edit parameters 404 * @param resourceInfo contains information about the resource to edit 405 * 406 * @return the start HTML for an enabled direct edit button 407 * @throws JSONException if a JSON handling error occurs 408 */ 409 public String startDirectEditEnabled(CmsDirectEditParams params, CmsDirectEditResourceInfo resourceInfo) 410 throws JSONException { 411 412 String editLocale = m_cms.getRequestContext().getLocale().toString(); 413 String editId = getNextDirectEditId(); 414 String editNewLink = CmsEncoder.encode(params.getLinkForNew()); 415 // putting together all needed data 416 JSONObject editableData = new JSONObject(); 417 CmsResource resource = resourceInfo.getResource(); 418 boolean writable = false; 419 String uri = m_cms.getRequestContext().getUri(); 420 uri = m_cms.getRequestContext().addSiteRoot(uri); 421 CmsADEConfigData configData = OpenCms.getADEManager().lookupConfigurationWithCache(m_cms, uri); 422 if (resource != null) { 423 try { 424 writable = m_cms.hasPermissions( 425 resource, 426 CmsPermissionSet.ACCESS_WRITE, 427 false, 428 CmsResourceFilter.IGNORE_EXPIRATION); 429 } catch (CmsException e) { 430 LOG.error(e.getLocalizedMessage(), e); 431 } 432 } 433 editableData.put("editId", editId); 434 CmsContainerElementBean containerElement = params.getContainerElement(); 435 if (containerElement != null) { 436 editableData.put(CmsGwtConstants.ATTR_ELEMENT_ID, containerElement.editorHash()); 437 } 438 439 editableData.put("structureId", resourceInfo.getResource().getStructureId()); 440 editableData.put("sitePath", params.getResourceName()); 441 editableData.put("elementlanguage", editLocale); 442 editableData.put("elementname", params.getElement()); 443 editableData.put("newlink", editNewLink); 444 editableData.put("hasResource", resource != null); 445 editableData.put("newtitle", m_messages.key(Messages.GUI_EDITOR_TITLE_NEW_0)); 446 editableData.put( 447 "unreleaseOrExpired", 448 !resourceInfo.getResource().isReleasedAndNotExpired(System.currentTimeMillis())); 449 if (params.getId() != null) { 450 editableData.put(CmsEditorConstants.ATTR_CONTEXT_ID, params.getId().toString()); 451 } 452 editableData.put(CmsEditorConstants.ATTR_POST_CREATE_HANDLER, params.getPostCreateHandler()); 453 CmsUUID viewId = CmsUUID.getNullUUID(); 454 boolean hasEditHandler = false; 455 String typeName = null; 456 if ((resourceInfo.getResource() != null) && resourceInfo.getResource().isFile()) { 457 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resourceInfo.getResource()); 458 if (type instanceof CmsResourceTypeXmlContent) { 459 hasEditHandler = ((CmsResourceTypeXmlContent)type).getEditHandler(m_cms) != null; 460 } 461 typeName = OpenCms.getResourceManager().getResourceType(resourceInfo.getResource()).getTypeName(); 462 CmsResourceTypeConfig typeConfig = configData.getResourceType(typeName); 463 if (typeConfig != null) { 464 viewId = typeConfig.getElementView(); 465 } 466 } else { 467 String linkForNew = params.getLinkForNew(); 468 if (linkForNew != null) { 469 String[] components = linkForNew.split("\\|"); 470 typeName = components[components.length - 1]; 471 CmsResourceTypeConfig typeConfig = configData.getResourceType(typeName); 472 if (typeConfig != null) { 473 viewId = typeConfig.getElementView(); 474 } 475 } 476 } 477 SitemapDirectEditPermissions sitemapConfigPermissions = configData.getDirectEditPermissions(typeName); 478 boolean hasNew = false; 479 editableData.put( 480 "hasEdit", 481 params.getButtonSelection().isShowEdit() && CmsResourceTypeXmlContent.isXmlContent(resource)); 482 editableData.put( 483 "hasDelete", 484 params.getButtonSelection().isShowDelete() && writable && sitemapConfigPermissions.canEdit()); 485 // hasNew = params.getButtonSelection().isShowNew() && writable && sitemapConfigPermissions.canEdit(); 486 487 editableData.put(CmsEditorConstants.ATTR_ELEMENT_VIEW, viewId); 488 editableData.put("hasEditHandler", hasEditHandler); 489 boolean favorites = sitemapConfigPermissions.canFavorite(); 490 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms); 491 CmsMessages messages = Messages.get().getBundle(locale); 492 if ((m_lastPermissionMode == 1) || !writable || (!sitemapConfigPermissions.canEdit())) { 493 String noEditReason = null; 494 if (sitemapConfigPermissions.canCreate()) { 495 noEditReason = messages.key(Messages.GUI_DIRECTEDIT_CAN_ONLY_BE_CREATED_0);// "Can only be created but not edited"; 496 } else { 497 noEditReason = messages.key(Messages.GUI_DIRECTEDIT_CANNOT_BE_CREATED_OR_EDITED_0); // "Cannot be created or edited"; 498 } 499 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(noEditReason)) { 500 editableData.put("noEditReason", noEditReason); 501 } 502 } 503 editableData.put(CmsGwtConstants.ATTR_FAVORITE, Boolean.valueOf(favorites)); 504 AutoBean<I_CmsEditableDataExtensions> extensions = m_editableDataExtensionsFactory.createExtensions(); 505 boolean isUploadType = false; 506 List<String> uploadTypes = Arrays.asList( 507 CmsResourceTypeBinary.getStaticTypeName(), 508 CmsResourceTypeImage.getStaticTypeName(), 509 CmsResourceTypePlain.getStaticTypeName()); 510 if ((resource != null) && !resource.isFolder()) { 511 for (String type : uploadTypes) { 512 if (OpenCms.getResourceManager().matchResourceType(type, resource.getTypeId())) { 513 isUploadType = true; 514 break; 515 } 516 } 517 } else { 518 String newLink = params.getLinkForNew(); 519 if (newLink != null) { 520 int pipePos = newLink.lastIndexOf('|'); 521 String typePart = newLink.substring(pipePos + 1); 522 isUploadType = uploadTypes.contains(typePart); 523 } 524 } 525 String uploadFolder = params.getUploadFolder(); 526 hasNew = params.getButtonSelection().isShowNew(); 527 if (isUploadType) { 528 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(uploadFolder) && !"none".equals(uploadFolder)) { 529 CmsResource uploadFolderResource = null; 530 try { 531 uploadFolderResource = m_cms.readResource(uploadFolder, CmsResourceFilter.IGNORE_EXPIRATION); 532 hasNew &= m_cms.hasPermissions( 533 uploadFolderResource, 534 CmsPermissionSet.ACCESS_WRITE, 535 false, 536 CmsResourceFilter.IGNORE_EXPIRATION); 537 } catch (CmsVfsResourceNotFoundException e) { 538 LOG.debug(e.getLocalizedMessage(), e); 539 } catch (CmsPermissionViolationException e) { 540 LOG.debug( 541 "hasNew = false for upload folder " + uploadFolder + ", reason: " + e.getLocalizedMessage(), 542 e); 543 hasNew = false; 544 } catch (CmsException e) { 545 LOG.error(e.getLocalizedMessage(), e); 546 hasNew = false; 547 } 548 } else { 549 hasNew = false; 550 } 551 } else { 552 hasNew &= sitemapConfigPermissions.canCreate(); 553 } 554 extensions.as().setUploadEnabled(isUploadType && hasUploadSupport() && (params.getUploadFolder() != null)); 555 extensions.as().setUploadFolder(params.getUploadFolder()); 556 editableData.put(CmsGwtConstants.ATTR_EXTENSIONS, AutoBeanCodex.encode(extensions).getPayload()); 557 editableData.put("hasNew", hasNew); 558 559 StringBuffer result = new StringBuffer(512); 560 if (m_useIds) { 561 result.append( 562 "<div id=\"" 563 + getRandomId() 564 + "\" class='" 565 + CmsGwtConstants.CLASS_EDITABLE 566 + "' " 567 + CmsGwtConstants.ATTR_DATA_EDITABLE 568 + "='").append(editableData.toString()).append("'></div>\n"); 569 } else { 570 result.append( 571 "<div class='" 572 + CmsGwtConstants.CLASS_EDITABLE 573 + "' " 574 + CmsGwtConstants.ATTR_DATA_EDITABLE 575 + "='").append(editableData.toString()).append("'></div>\n"); 576 } 577 return result.toString(); 578 } 579 580 /** 581 * Checks if upload for binaries are supported by this provider. 582 * 583 * @return true if binary upload is supported 584 */ 585 protected boolean hasUploadSupport() { 586 587 return true; 588 } 589 590}