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