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