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 GmbH & Co. KG, 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.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.file.collectors.I_CmsResourceCollector; 033import org.opencms.file.history.CmsHistoryResourceHandler; 034import org.opencms.flex.CmsFlexController; 035import org.opencms.i18n.CmsEncoder; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.util.CmsStringUtil; 040import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection; 041import org.opencms.workplace.editors.directedit.CmsDirectEditJspIncludeProvider; 042import org.opencms.workplace.editors.directedit.CmsDirectEditMode; 043import org.opencms.workplace.editors.directedit.CmsDirectEditParams; 044import org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider; 045 046import javax.servlet.ServletRequest; 047import javax.servlet.jsp.JspException; 048import javax.servlet.jsp.PageContext; 049import javax.servlet.jsp.tagext.BodyTagSupport; 050import javax.servlet.jsp.tagext.Tag; 051 052import org.apache.commons.logging.Log; 053 054/** 055 * Implementation of the <code><cms:editable/></code> tag.<p> 056 * 057 * This class is also used to generate the direct edit buttons for the 058 * <code><cms:include editable="..." /></code> and <code><cms:contentload editable="..." /></code> tags.<p> 059 * 060 * Since OpenCms version 6.2.3, the direct edit button HTML generated is controlled by an instance of {@link I_CmsDirectEditProvider}. 061 * The default direct edit provider used can be configured in <code>opencms-workplace.xml</code> in the 062 * <code><directeditprovider class="..." /></code> node. The standard provider is 063 * {@link org.opencms.workplace.editors.directedit.CmsDirectEditDefaultProvider}. 064 * It's possible to override the default provider onm a page-by-page basis by initializing direct edit with 065 * <code><cms:editable provider="...." /></code> on top of the page.<p> 066 * 067 * Since OpenCms version 6.2.3, it is also possible to place the HTML of the direct edit buttons manually. 068 * This is intended for pages where the template HTML is not compatible with the direct edit HTML, 069 * which usually results in a funny placement of the direct edit buttons in a totally wrong position. 070 * To do manual placement of the direct edit buttons, you need to place <code><cms:editable mode="manual"></code> and 071 * <code></cms:editable></code> around your HTML. Both tags (start and end) will insert HTML according to 072 * the used {@link I_CmsDirectEditProvider}. The direct edit provider used must also support manual 073 * placing, or the manual tags will be ignored and the HTML will be inserted at the automatic position. 074 * A provider which support manual placing is the {@link org.opencms.workplace.editors.directedit.CmsDirectEditTextButtonProvider}.<p> 075 * 076 * @since 6.0.0 077 */ 078public class CmsJspTagEditable extends BodyTagSupport { 079 080 /** The log object for this class. */ 081 private static final Log LOG = CmsLog.getLog(CmsJspTagEditable.class); 082 083 /** Serial version UID required for safe serialization. */ 084 private static final long serialVersionUID = 4137789622146499225L; 085 086 /** File with editable elements. */ 087 protected String m_file; 088 089 /** Indicates which direct edit mode is active. */ 090 protected transient CmsDirectEditMode m_mode; 091 092 /** Class name of the direct edit provider. */ 093 protected String m_provider; 094 095 /** The edit empty tag attribute. */ 096 private boolean m_editEmpty; 097 098 /** Indicates if the tag is the first on the page, this mean the header file must be included. */ 099 private boolean m_firstOnPage; 100 101 /** Indicates if the direct edit HTML is to be placed manually. */ 102 private boolean m_manualPlacement; 103 104 /** 105 * Editable action method.<p> 106 * 107 * @param context the current JSP page context 108 * @param provider the class name of the direct edit privider to use (may be <code>null</code>, which means use the default) 109 * @param mode the direct edit mode to use (may be <code>null</code>, which means current use mode on page) 110 * @param fileName optional filename parameter for the direct edit provider (may be <code>null</code>, which means use the default) 111 * 112 * @throws JspException in case something goes wrong 113 */ 114 public static void editableTagAction(PageContext context, String provider, CmsDirectEditMode mode, String fileName) 115 throws JspException { 116 117 ServletRequest req = context.getRequest(); 118 if ((mode == CmsDirectEditMode.FALSE) || !isEditableRequest(req)) { 119 // direct edit is turned off 120 return; 121 } 122 123 CmsFlexController controller = CmsFlexController.getController(req); 124 CmsObject cms = controller.getCmsObject(); 125 I_CmsDirectEditProvider eb = getDirectEditProvider(context); 126 if (eb == null) { 127 if (CmsStringUtil.isNotEmpty(fileName) && CmsStringUtil.isEmpty(provider)) { 128 // if only a filename but no provider class is given, use JSP includes for backward compatibility 129 provider = CmsDirectEditJspIncludeProvider.class.getName(); 130 } 131 // no provider available in page context 132 if (CmsStringUtil.isNotEmpty(provider)) { 133 try { 134 // create a new instance of the selected provider 135 eb = (I_CmsDirectEditProvider)Class.forName(provider).newInstance(); 136 } catch (Exception e) { 137 // log error 138 LOG.error(Messages.get().getBundle().key(Messages.ERR_DIRECT_EDIT_PROVIDER_1, provider), e); 139 } 140 } 141 if (eb == null) { 142 // use configured direct edit provider as a fallback 143 eb = OpenCms.getWorkplaceManager().getDirectEditProvider(); 144 } 145 if (mode == null) { 146 // use automatic placement by default 147 mode = CmsDirectEditMode.AUTO; 148 } 149 eb.init(cms, mode, fileName); 150 // store the provider in the page context 151 setDirectEditProvider(context, eb); 152 } 153 if (eb.isManual(mode)) { 154 // manual mode, insert required HTML 155 CmsDirectEditParams params = getDirectEditProviderParams(context); 156 if (params != null) { 157 // insert direct edit start HTML 158 eb.insertDirectEditStart(context, params); 159 } else { 160 // insert direct edit end HTML 161 eb.insertDirectEditEnd(context); 162 } 163 } else { 164 // insert direct edit header HTML 165 eb.insertDirectEditIncludes(context, new CmsDirectEditParams(cms.getRequestContext().getUri())); 166 } 167 } 168 169 /** 170 * Closes the current direct edit element.<p> 171 * 172 * @param context the current JSP page context 173 * 174 * @throws JspException in case something goes wrong 175 */ 176 public static void endDirectEdit(PageContext context) throws JspException { 177 178 // get the direct edit bean from the context 179 I_CmsDirectEditProvider eb = getDirectEditProvider(context); 180 181 if (eb != null) { 182 // the direct edit bean must be available 183 eb.insertDirectEditEnd(context); 184 } 185 } 186 187 /** 188 * Returns the current initialized instance of the direct edit provider.<p> 189 * 190 * @param context the current JSP page context 191 * 192 * @return the current initialized instance of the direct edit provider 193 */ 194 public static I_CmsDirectEditProvider getDirectEditProvider(PageContext context) { 195 196 // get the direct edit provider from the request attributes 197 return (I_CmsDirectEditProvider)context.getRequest().getAttribute( 198 I_CmsDirectEditProvider.ATTRIBUTE_DIRECT_EDIT_PROVIDER); 199 } 200 201 /** 202 * Inserts direct edit for empty collector lists.<p> 203 * 204 * @param context the current JSP page context 205 * @param container the parent content load container 206 * @param mode the direct edit mode to use (may be <code>null</code>, which means current use mode on page) 207 * @param id an id for the content info bean, if available 208 * 209 * @throws CmsException in case of invalid collector settings 210 * @throws JspException in case writing to page context fails 211 */ 212 public static void insertEditEmpty( 213 PageContext context, 214 I_CmsXmlContentContainer container, 215 CmsDirectEditMode mode, 216 String id) 217 throws CmsException, JspException { 218 219 ServletRequest req = context.getRequest(); 220 if (isEditableRequest(req) 221 && (container.getCollectorResult() != null) 222 && (container.getCollectorResult().size() == 0)) { 223 CmsFlexController controller = CmsFlexController.getController(req); 224 CmsObject cms = controller.getCmsObject(); 225 // now collect the resources 226 I_CmsResourceCollector collector = OpenCms.getResourceManager().getContentCollector( 227 container.getCollectorName()); 228 if (collector == null) { 229 throw new CmsException( 230 Messages.get().container(Messages.ERR_COLLECTOR_NOT_FOUND_1, container.getCollectorName())); 231 } 232 String createParam = collector.getCreateParam( 233 cms, 234 container.getCollectorName(), 235 container.getCollectorParam()); 236 String createLink = collector.getCreateLink( 237 cms, 238 container.getCollectorName(), 239 container.getCollectorParam()); 240 if ((createParam != null) 241 && (collector.getCreateTypeId( 242 cms, 243 container.getCollectorName(), 244 container.getCollectorParam()) != -1)) { 245 String createFolderName = CmsResource.getFolderPath(createLink); 246 createParam = CmsEncoder.encode(container.getCollectorName() + "|" + createParam); 247 CmsDirectEditParams params = new CmsDirectEditParams( 248 createFolderName, 249 CmsDirectEditButtonSelection.NEW, 250 mode, 251 createParam); 252 params.setId(id); 253 params.setCollectorName(container.getCollectorName()); 254 params.setCollectorParams(container.getCollectorParam()); 255 getDirectEditProvider(context).insertDirectEditEmptyList(context, params); 256 } 257 } 258 } 259 260 /** 261 * Checks if the current request should be direct edit enabled.<p> 262 * 263 * @param req the servlet request 264 * 265 * @return <code>true</code> if the current request should be direct edit enabled 266 */ 267 public static boolean isEditableRequest(ServletRequest req) { 268 269 boolean result = false; 270 if (CmsHistoryResourceHandler.isHistoryRequest(req) || CmsJspTagEnableAde.isDirectEditDisabled(req)) { 271 // don't display direct edit buttons on an historical resource 272 result = false; 273 } else { 274 CmsFlexController controller = CmsFlexController.getController(req); 275 CmsObject cms = controller.getCmsObject(); 276 result = !cms.getRequestContext().getCurrentProject().isOnlineProject() 277 && !CmsResource.isTemporaryFileName(cms.getRequestContext().getUri()); 278 279 } 280 return result; 281 } 282 283 /** 284 * Includes the "direct edit" start element that adds HTML for the editable area to 285 * the output page.<p> 286 * 287 * @param context the current JSP page context 288 * @param params the direct edit parameters 289 * 290 * @return <code>true</code> in case a direct edit element has been opened 291 * 292 * @throws JspException in case something goes wrong 293 */ 294 public static boolean startDirectEdit(PageContext context, CmsDirectEditParams params) throws JspException { 295 296 // get the direct edit bean from the context 297 I_CmsDirectEditProvider eb = getDirectEditProvider(context); 298 299 boolean result = false; 300 if (eb != null) { 301 // the direct edit bean must be available 302 if (eb.isManual(params.getMode())) { 303 // store the given parameters for the next manual call 304 setDirectEditProviderParams(context, params); 305 } else { 306 // automatic mode, insert direct edit HTML 307 result = eb.insertDirectEditStart(context, params); 308 } 309 } 310 311 return result; 312 } 313 314 /** 315 * Returns the current initialized instance of the direct edit provider parameters from the given page context.<p> 316 * 317 * Also removes the parameters from the given page context.<p> 318 * 319 * @param context the current JSP page context 320 * 321 * @return the current initialized instance of the direct edit provider parameters 322 */ 323 protected static CmsDirectEditParams getDirectEditProviderParams(PageContext context) { 324 325 // get the current request 326 ServletRequest req = context.getRequest(); 327 // get the direct edit params from the request attributes 328 CmsDirectEditParams result = (CmsDirectEditParams)req.getAttribute( 329 I_CmsDirectEditProvider.ATTRIBUTE_DIRECT_EDIT_PROVIDER_PARAMS); 330 if (result != null) { 331 req.removeAttribute(I_CmsDirectEditProvider.ATTRIBUTE_DIRECT_EDIT_PROVIDER_PARAMS); 332 } 333 return result; 334 } 335 336 /** 337 * Sets the current initialized instance of the direct edit provider.<p> 338 * 339 * @param context the current JSP page context 340 * 341 * @param provider the current initialized instance of the direct edit provider to set 342 */ 343 protected static void setDirectEditProvider(PageContext context, I_CmsDirectEditProvider provider) { 344 345 // set the direct edit provider as attribute to the request 346 context.getRequest().setAttribute(I_CmsDirectEditProvider.ATTRIBUTE_DIRECT_EDIT_PROVIDER, provider); 347 } 348 349 /** 350 * Sets the current initialized instance of the direct edit provider parameters to the page context.<p> 351 * 352 * @param context the current JSP page context 353 * @param params the current initialized instance of the direct edit provider parameters to set 354 */ 355 protected static void setDirectEditProviderParams(PageContext context, CmsDirectEditParams params) { 356 357 // set the direct edit params as attribute to the request 358 context.getRequest().setAttribute(I_CmsDirectEditProvider.ATTRIBUTE_DIRECT_EDIT_PROVIDER_PARAMS, params); 359 } 360 361 /** 362 * Close the direct edit tag, also prints the direct edit HTML to the current page.<p> 363 * 364 * @return {@link #EVAL_PAGE} 365 * 366 * @throws JspException in case something goes wrong 367 */ 368 @Override 369 public int doEndTag() throws JspException { 370 371 if (m_firstOnPage || m_manualPlacement) { 372 // only execute action for the first "editable" tag on the page (include file), or in manual mode 373 editableTagAction(pageContext, m_provider, m_mode, m_file); 374 } 375 if (OpenCms.getSystemInfo().getServletContainerSettings().isReleaseTagsAfterEnd()) { 376 // need to release manually, JSP container may not call release as required (happens with Tomcat) 377 release(); 378 } 379 380 return EVAL_PAGE; 381 } 382 383 /** 384 * Opens the direct edit tag, if manual mode is set then the next 385 * start HTML for the direct edit buttons is printed to the page.<p> 386 * 387 * @return {@link #EVAL_BODY_INCLUDE} 388 * 389 * @throws JspException in case something goes wrong 390 */ 391 @Override 392 public int doStartTag() throws JspException { 393 394 if (isEditableRequest(pageContext.getRequest())) { 395 // all this does NOT apply to the "online" project, or for temporary files 396 I_CmsDirectEditProvider eb = getDirectEditProvider(pageContext); 397 // if no provider is available this is the first "editable" tag on the page 398 m_firstOnPage = (eb == null); 399 m_manualPlacement = false; 400 if ((m_mode == CmsDirectEditMode.MANUAL) || !m_editEmpty) { 401 // manual mode requested, we may need to insert HTML 402 if (!m_firstOnPage && (eb != null)) { 403 // first tag on a page is only for insertion of header HTML 404 if (eb.isManual(m_mode)) { 405 // the provider supports manual placement of buttons 406 m_manualPlacement = true; 407 editableTagAction(pageContext, m_provider, m_mode, m_file); 408 } 409 } 410 } else if (!m_firstOnPage && (eb != null) && m_editEmpty) { 411 try { 412 insertEditEmpty(); 413 } catch (CmsException e) { 414 LOG.error(e.getLocalizedMessage(), e); 415 } 416 } 417 } else { 418 // this will ensure the "end" tag is also ignored in the online project 419 m_firstOnPage = false; 420 m_manualPlacement = false; 421 } 422 return EVAL_BODY_INCLUDE; 423 } 424 425 /** 426 * Gets the file with elements for direct editing.<p> 427 * 428 * @return the file 429 */ 430 public String getFile() { 431 432 return m_file != null ? m_file : ""; 433 } 434 435 /** 436 * Returns the direct edit mode.<p> 437 * 438 * @return the direct edit mode 439 */ 440 public String getMode() { 441 442 return m_mode != null ? m_mode.toString() : ""; 443 } 444 445 /** 446 * Returns the class name of the direct edit provider.<p> 447 * 448 * @return the class name of the direct edit provider 449 */ 450 public String getProvider() { 451 452 return m_provider != null ? m_provider : ""; 453 } 454 455 /** 456 * Returns the edit empty attribute.<p> 457 * 458 * @return the edit empty attribute 459 */ 460 public boolean isEditEmpty() { 461 462 return m_editEmpty; 463 } 464 465 /** 466 * Releases any resources we may have (or inherit).<p> 467 */ 468 @Override 469 public void release() { 470 471 super.release(); 472 m_file = null; 473 m_provider = null; 474 m_mode = null; 475 m_firstOnPage = false; 476 m_manualPlacement = true; 477 m_editEmpty = false; 478 } 479 480 /** 481 * Sets the edit empty attribute.<p> 482 * 483 * @param editEmpty the edit empty attribute to set 484 */ 485 public void setEditEmpty(boolean editEmpty) { 486 487 m_editEmpty = editEmpty; 488 } 489 490 /** 491 * Sets the file with elements for direct editing.<p> 492 * 493 * @param file the file to set 494 */ 495 public void setFile(String file) { 496 497 m_file = file; 498 } 499 500 /** 501 * Sets the direct edit mode.<p> 502 * 503 * @param mode the direct edit mode to set 504 */ 505 public void setMode(String mode) { 506 507 m_mode = CmsDirectEditMode.valueOf(mode); 508 } 509 510 /** 511 * Sets the class name of the direct edit provider.<p> 512 * 513 * @param provider the class name of the direct edit provider to set 514 */ 515 public void setProvider(String provider) { 516 517 m_provider = provider; 518 } 519 520 /** 521 * Inserts direct edit for empty collector lists.<p> 522 * 523 * @throws CmsException in case of invalid collector settings 524 * @throws JspException in case writing to page context fails 525 */ 526 private void insertEditEmpty() throws CmsException, JspException { 527 528 Tag ancestor = findAncestorWithClass(this, I_CmsXmlContentContainer.class); 529 I_CmsXmlContentContainer container = null; 530 if (ancestor != null) { 531 // parent content container available, use preloaded values from this container 532 container = (I_CmsXmlContentContainer)ancestor; 533 insertEditEmpty(pageContext, container, m_mode == null ? CmsDirectEditMode.AUTO : m_mode, null); 534 } 535 } 536}