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.jsp; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsResourceTypeConfig; 032import org.opencms.ade.contenteditor.shared.CmsEditorConstants; 033import org.opencms.file.CmsFile; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.collectors.A_CmsResourceCollector; 038import org.opencms.file.collectors.I_CmsCollectorPostCreateHandler; 039import org.opencms.file.types.I_CmsResourceType; 040import org.opencms.flex.CmsFlexController; 041import org.opencms.loader.CmsLoaderException; 042import org.opencms.main.CmsException; 043import org.opencms.main.CmsIllegalArgumentException; 044import org.opencms.main.CmsLog; 045import org.opencms.main.OpenCms; 046import org.opencms.util.CmsPair; 047import org.opencms.util.CmsStringUtil; 048import org.opencms.util.CmsUUID; 049import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection; 050import org.opencms.workplace.editors.directedit.CmsDirectEditParams; 051 052import java.util.Locale; 053 054import javax.servlet.jsp.JspException; 055import javax.servlet.jsp.PageContext; 056 057import org.apache.commons.lang3.StringUtils; 058import org.apache.commons.logging.Log; 059 060/** This tag is used to attach an edit provider to a snippet of HTML. */ 061public class CmsJspTagEdit extends CmsJspScopedVarBodyTagSuport { 062 063 /** Identifier to indicate that the new link should be handled by this tag - not by a {@link org.opencms.file.collectors.I_CmsResourceCollector}. */ 064 public static final String NEW_LINK_IDENTIFIER = "__edit__"; 065 066 /** The log object for this class. */ 067 private static final Log LOG = CmsLog.getLog(CmsJspTagEdit.class); 068 069 /** Serial version UID required for safe serialization. */ 070 private static final long serialVersionUID = -3781368910893187306L; 071 072 /** Flag, indicating if the create option should be displayed. */ 073 private boolean m_canCreate; 074 075 /** Flag, indicating if the delete option should be displayed. */ 076 private boolean m_canDelete; 077 078 /** The type of the resource that should be created. */ 079 private String m_createType; 080 081 /** The tag attribute's value, specifying the path to the (sub)sitemap where new content should be created. */ 082 private String m_creationSiteMap; 083 084 /** Flag, indicating if during rendering the "startDirectEdit" part has been rendered, but not the "endDirectEdit" part. */ 085 private boolean m_isEditOpen; 086 087 /** The fully qualified class name of the post create handler to use. */ 088 private String m_postCreateHandler; 089 090 /** The upload folder. */ 091 private String m_uploadFolder; 092 093 /** UUID of the content to edit. */ 094 private String m_uuid; 095 096 /** Creates a new resource. 097 * @param cmsObject The CmsObject of the current request context. 098 * @param newLink A string, specifying where which new content should be created. 099 * @param locale The locale for which the 100 * @param sitePath site path of the currently edited content. 101 * @param modelFileName not used. 102 * @param mode optional creation mode 103 * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created. 104 * The fully qualified class name can be followed by a "|" symbol and a handler specific configuration string. 105 * @return The site-path of the newly created resource. 106 * @throws CmsException if something goes wrong 107 */ 108 public static String createResource( 109 CmsObject cmsObject, 110 String newLink, 111 Locale locale, 112 String sitePath, 113 String modelFileName, 114 String mode, 115 String postCreateHandler) 116 throws CmsException { 117 118 String[] newLinkParts = newLink.split("\\|"); 119 String rootPath = newLinkParts[1]; 120 String typeName = newLinkParts[2]; 121 CmsFile modelFile = null; 122 if (StringUtils.equalsIgnoreCase(mode, CmsEditorConstants.MODE_COPY)) { 123 try { 124 modelFile = cmsObject.readFile(sitePath); 125 } catch (CmsException e) { 126 LOG.warn( 127 "The resource at path" + sitePath + "could not be read. Thus it can not be used as model file.", 128 e); 129 } 130 } 131 CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfiguration(cmsObject, rootPath); 132 CmsResourceTypeConfig typeConfig = adeConfig.getResourceType(typeName); 133 CmsResource newElement = null; 134 135 CmsObject cmsClone = cmsObject; 136 if ((locale != null) && !cmsObject.getRequestContext().getLocale().equals(locale)) { 137 // in case the content locale does not match the request context locale, use a clone cms with the appropriate locale 138 cmsClone = OpenCms.initCmsObject(cmsObject); 139 cmsClone.getRequestContext().setLocale(locale); 140 } 141 newElement = typeConfig.createNewElement(cmsClone, modelFile, rootPath); 142 CmsPair<String, String> handlerParameter = I_CmsCollectorPostCreateHandler.splitClassAndConfig( 143 postCreateHandler); 144 I_CmsCollectorPostCreateHandler handler = A_CmsResourceCollector.getPostCreateHandler( 145 handlerParameter.getFirst()); 146 handler.onCreate(cmsClone, cmsClone.readFile(newElement), modelFile != null, handlerParameter.getSecond()); 147 return newElement == null ? null : cmsObject.getSitePath(newElement); 148 } 149 150 /** 151 * Creates the String specifying where which type of resource has to be created.<p> 152 * 153 * @param cms the CMS context 154 * @param resType the resource type to create 155 * @param creationSitemap the creation sitemap parameter 156 * 157 * @return The String identifying which type of resource has to be created where.<p> 158 * 159 * @see #createResource(CmsObject, String, Locale, String, String, String, String) 160 */ 161 public static String getNewLink(CmsObject cms, I_CmsResourceType resType, String creationSitemap) { 162 163 String contextPath = getContextRootPath(cms, creationSitemap); 164 StringBuffer newLink = new StringBuffer(NEW_LINK_IDENTIFIER); 165 newLink.append('|'); 166 newLink.append(contextPath); 167 newLink.append('|'); 168 newLink.append(resType.getTypeName()); 169 170 return newLink.toString(); 171 } 172 173 /** 174 * Returns the resource type name contained in the newLink parameter.<p> 175 * 176 * @param newLink the newLink parameter 177 * 178 * @return the resource type name 179 */ 180 public static String getRootPathFromNewLink(String newLink) { 181 182 String result = null; 183 if (newLink.startsWith(NEW_LINK_IDENTIFIER) && newLink.contains("|")) { 184 result = newLink.substring(newLink.indexOf("|") + 1, newLink.lastIndexOf("|")); 185 } 186 return result; 187 } 188 189 /** 190 * Returns the resource type name contained in the newLink parameter.<p> 191 * 192 * @param newLink the newLink parameter 193 * 194 * @return the resource type name 195 */ 196 public static String getTypeFromNewLink(String newLink) { 197 198 String result = null; 199 if (newLink.startsWith(NEW_LINK_IDENTIFIER) && newLink.contains("|")) { 200 result = newLink.substring(newLink.lastIndexOf("|") + 1); 201 } 202 203 return result; 204 } 205 206 /** 207 * Inserts the closing direct edit tag.<p> 208 * 209 * @param pageContext the page context 210 */ 211 public static void insertDirectEditEnd(PageContext pageContext) { 212 213 try { 214 CmsJspTagEditable.endDirectEdit(pageContext); 215 } catch (JspException e) { 216 LOG.error("Could not print closing direct edit tag.", e); 217 } 218 } 219 220 /** 221 * Inserts the opening direct edit tag.<p> 222 * 223 * @param cms the CMS context 224 * @param pageContext the page context 225 * @param resource the resource to edit 226 * @param canCreate if resource creation is allowed 227 * @param canDelete if resource deletion is allowed 228 * @param createType the resource type to create, default to the type of the edited resource 229 * @param creationSitemap the sitemap context to create the resource in, default to the current requested URI 230 * @param postCreateHandler the post create handler if required 231 * @param binaryUploadFolder the upload folder for binary files 232 * 233 * @return <code>true</code> if an opening direct edit tag was inserted 234 */ 235 public static boolean insertDirectEditStart( 236 CmsObject cms, 237 PageContext pageContext, 238 CmsResource resource, 239 boolean canCreate, 240 boolean canDelete, 241 String createType, 242 String creationSitemap, 243 String postCreateHandler, 244 String binaryUploadFolder) { 245 246 boolean result = false; 247 CmsDirectEditParams editParams = null; 248 if (resource != null) { 249 250 String newLink = null; 251 // reconstruct create type from the edit-resource if necessary 252 if (canCreate) { 253 I_CmsResourceType resType = getResourceType(resource, createType); 254 if (resType != null) { 255 newLink = getNewLink(cms, resType, creationSitemap); 256 } 257 } 258 CmsDirectEditButtonSelection buttons = null; 259 if (canDelete) { 260 if (newLink != null) { 261 buttons = CmsDirectEditButtonSelection.EDIT_DELETE_NEW; 262 } else { 263 buttons = CmsDirectEditButtonSelection.EDIT_DELETE; 264 } 265 } else if (newLink != null) { 266 buttons = CmsDirectEditButtonSelection.EDIT_NEW; 267 } else { 268 buttons = CmsDirectEditButtonSelection.EDIT; 269 } 270 editParams = new CmsDirectEditParams(cms.getSitePath(resource), buttons, null, newLink); 271 } else if (canCreate) { 272 I_CmsResourceType resType = getResourceType(null, createType); 273 if (resType != null) { 274 editParams = new CmsDirectEditParams( 275 cms.getRequestContext().getFolderUri(), 276 CmsDirectEditButtonSelection.NEW, 277 null, 278 getNewLink(cms, resType, creationSitemap)); 279 } 280 } 281 282 if (editParams != null) { 283 editParams.setPostCreateHandler(postCreateHandler); 284 editParams.setUploadFolder(binaryUploadFolder); 285 try { 286 CmsJspTagEditable.startDirectEdit(pageContext, editParams); 287 result = true; 288 } catch (JspException e) { 289 // TODO: Localize and improve error message. 290 LOG.error("Could not create direct edit start.", e); 291 } 292 } 293 return result; 294 } 295 296 /** 297 * Returns the context root path.<p> 298 * 299 * @param cms the CMS context 300 * @param creationSitemap the creation sitemap parameter 301 * 302 * @return the context root path 303 */ 304 private static String getContextRootPath(CmsObject cms, String creationSitemap) { 305 306 String path = null; 307 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(creationSitemap)) { 308 try { 309 path = cms.readFolder(creationSitemap).getRootPath(); 310 } catch (CmsException e) { 311 LOG.warn("The provided creation sitemap " + creationSitemap + " is not a VFS folder.", e); 312 } 313 } 314 if (path == null) { 315 path = cms.addSiteRoot(cms.getRequestContext().getFolderUri()); 316 } 317 318 return path; 319 } 320 321 /** 322 * Returns the resource type to create, or <code>null</code> if not available.<p> 323 * 324 * @param resource the edit resource 325 * @param createType the create type parameter 326 * 327 * @return the resource type 328 */ 329 private static I_CmsResourceType getResourceType(CmsResource resource, String createType) { 330 331 I_CmsResourceType resType = null; 332 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(createType)) { 333 try { 334 resType = OpenCms.getResourceManager().getResourceType(createType); 335 } catch (CmsLoaderException e) { 336 LOG.error("Could not read resource type '" + createType + "' for resource creation.", e); 337 } 338 } else if (resource != null) { 339 resType = OpenCms.getResourceManager().getResourceType(resource); 340 } 341 return resType; 342 } 343 344 /** 345 * @see javax.servlet.jsp.tagext.BodyTagSupport#doEndTag() 346 */ 347 @Override 348 public int doEndTag() throws JspException { 349 350 if (m_isEditOpen) { 351 CmsJspTagEditable.endDirectEdit(pageContext); 352 } 353 release(); 354 return EVAL_PAGE; 355 } 356 357 /** 358 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 359 */ 360 @Override 361 public int doStartTag() throws CmsIllegalArgumentException { 362 363 CmsObject cms = getCmsObject(); 364 m_isEditOpen = insertDirectEditStart( 365 cms, 366 pageContext, 367 getResourceToEdit(cms), 368 m_canCreate || (null != m_createType), 369 m_canDelete, 370 m_createType, 371 m_creationSiteMap, 372 m_postCreateHandler, 373 m_uploadFolder); 374 return EVAL_BODY_INCLUDE; 375 } 376 377 /** 378 * @see org.opencms.jsp.CmsJspScopedVarBodyTagSuport#release() 379 */ 380 @Override 381 public void release() { 382 383 m_canCreate = false; 384 m_canDelete = false; 385 m_creationSiteMap = null; 386 m_createType = null; 387 m_isEditOpen = false; 388 m_uuid = null; 389 super.release(); 390 } 391 392 /** Setter for the "create" attribute of the tag. 393 * @param canCreate value of the tag's attribute "create". 394 */ 395 public void setCreate(final Boolean canCreate) { 396 397 m_canCreate = canCreate == null ? false : canCreate.booleanValue(); 398 } 399 400 /** Setter for the "createType" attribute of the tag.<p> 401 * 402 * @param typeName value of the "createType" attribute of the tag. 403 */ 404 public void setCreateType(final String typeName) { 405 406 m_createType = typeName; 407 } 408 409 /** Setter for the "creationSiteMap" attribute of the tag. 410 * 411 * @param sitePath value of the "creationSiteMap" attribute of the tag. 412 */ 413 public void setCreationSiteMap(final String sitePath) { 414 415 m_creationSiteMap = sitePath; 416 } 417 418 /**Setter for the "delete" attribute of the tag. 419 * @param canDelete value of the "delete" attribute of the tag. 420 */ 421 public void setDelete(final Boolean canDelete) { 422 423 m_canDelete = canDelete == null ? false : canDelete.booleanValue(); 424 } 425 426 /** Setter for the "postCreateHandler" attribute of the tag. 427 * @param postCreateHandler fully qualified class name of the {@link I_CmsCollectorPostCreateHandler} to use. 428 */ 429 public void setPostCreateHandler(final String postCreateHandler) { 430 431 m_postCreateHandler = postCreateHandler; 432 } 433 434 /** 435 * Sets the upload folder. 436 * 437 * @param uploadFolder the upload folder 438 */ 439 public void setUploadFolder(String uploadFolder) { 440 441 m_uploadFolder = uploadFolder; 442 } 443 444 /** Setter for the uuid attribute of the tag, providing the uuid of content that should be edited. 445 * If no valid uuid of an existing resource is given, it is assumed the tag is only used for creating new contents. 446 * @param uuid the uuid of the content that should be edited. 447 */ 448 public void setUuid(final String uuid) { 449 450 m_uuid = uuid; 451 } 452 453 /** 454 * Returns the current CMS context.<p> 455 * 456 * @return the CMS context 457 */ 458 private CmsObject getCmsObject() { 459 460 CmsFlexController controller = CmsFlexController.getController(pageContext.getRequest()); 461 return controller.getCmsObject(); 462 463 } 464 465 /** 466 * Returns the resource to edit according to the uuid provided via the tag's attribute "uuid".<p> 467 * 468 * @param cms the CMS context 469 * 470 * @return the resource 471 */ 472 private CmsResource getResourceToEdit(CmsObject cms) { 473 474 CmsResource resource = null; 475 if (m_uuid != null) { 476 try { 477 CmsUUID uuid = new CmsUUID(m_uuid); 478 resource = cms.readResource(uuid, CmsResourceFilter.ignoreExpirationOffline(cms)); 479 480 } catch (NumberFormatException | CmsException e) { 481 LOG.warn("UUID was not valid or there is no resource with the given UUID.", e); 482 } 483 } 484 return resource; 485 } 486}