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}