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>&lt;cms:editable/&gt;</code> tag.<p>
056 *
057 * This class is also used to generate the direct edit buttons for the
058 * <code>&lt;cms:include editable="..." /&gt;</code> and <code>&lt;cms:contentload editable="..." /&gt;</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>&lt;directeditprovider class="..." /&gt;</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>&lt;cms:editable provider="...." /&gt;</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>&lt;cms:editable mode="manual"&gt;</code> and
071 * <code>&lt;/cms:editable&gt;</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}