/*
 * File   : $Source: /usr/local/cvs/opencms/src/org/opencms/workplace/editors/CmsEditor.java,v $
 * Date   : $Date: 2008-05-26 12:37:25 $
 * Version: $Revision: 1.55 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.workplace.editors;

import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.i18n.CmsEncoder;
import org.opencms.jsp.CmsJspActionElement;
import org.opencms.lock.CmsLock;
import org.opencms.lock.CmsLockType;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsPermissionSet;
import org.opencms.security.I_CmsPrincipal;
import org.opencms.util.CmsStringUtil;
import org.opencms.workplace.CmsFrameset;
import org.opencms.workplace.CmsWorkplace;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import javax.servlet.ServletException;
import javax.servlet.jsp.JspException;

import org.apache.commons.logging.Log;

/**
 * Provides basic methods for building the file editors of OpenCms.<p> 
 * 
 * The editor classes have to extend this class and implement action methods for common editor actions.<p>
 *
 * @author  Andreas Zahner 
 * 
 * @version $Revision: 1.55 $ 
 * 
 * @since 6.0.0 
 */
public abstract class CmsEditor extends CmsEditorBase {

    /** Value for the action: change the body. */
    public static final int ACTION_CHANGE_BODY = 124;

    /** Value for the action: delete the current locale. */
    public static final int ACTION_DELETELOCALE = 140;

    /** Value for the action: exit. */
    public static final int ACTION_EXIT = 122;

    /** Value for the action: show a preview. */
    public static final int ACTION_PREVIEW = 126;

    /** Value for the action: save. */
    public static final int ACTION_SAVE = 121;

    /** Constant value for the customizable action button. */
    public static final int ACTION_SAVEACTION = 130;

    /** Value for the action: save and exit. */
    public static final int ACTION_SAVEEXIT = 123;

    /** Value for the action: show the editor. */
    public static final int ACTION_SHOW = 125;

    /** Value for the action: an error occurred. */
    public static final int ACTION_SHOW_ERRORMESSAGE = 127;

    /** Value for the action parameter: change the element. */
    public static final String EDITOR_CHANGE_ELEMENT = "changeelement";

    /** Value for the action parameter: cleanup content. */
    public static final String EDITOR_CLEANUP = "cleanup";

    /** Value for the action parameter: close browser window (accidentally). */
    public static final String EDITOR_CLOSEBROWSER = "closebrowser";

    /** Value for the action parameter: delete the current locale. */
    public static final String EDITOR_DELETELOCALE = "deletelocale";

    /** Value for the action parameter: exit editor. */
    public static final String EDITOR_EXIT = "exit";

    /** Value for the action parameter: show a preview. */
    public static final String EDITOR_PREVIEW = "preview";

    /** Value for the action parameter: save content. */
    public static final String EDITOR_SAVE = "save";

    /** Value for the customizable action button. */
    public static final String EDITOR_SAVEACTION = "saveaction";

    /** Value for the action parameter: save and exit. */
    public static final String EDITOR_SAVEEXIT = "saveexit";

    /** Value for the action parameter: show the editor. */
    public static final String EDITOR_SHOW = "show";

    /** Value for the action parameter: an error occurred. */
    public static final String EDITOR_SHOW_ERRORMESSAGE = "error";

    /** Marker for empty locale in locale selection. */
    public static final String EMPTY_LOCALE = " [-]";

    /** Parameter name for the request parameter "backlink". */
    public static final String PARAM_BACKLINK = "backlink";

    /** Parameter name for the request parameter "content". */
    public static final String PARAM_CONTENT = "content";

    /** Parameter name for the request parameter "directedit". */
    public static final String PARAM_DIRECTEDIT = "directedit";

    /** Parameter name for the request parameter "editastext". */
    public static final String PARAM_EDITASTEXT = "editastext";

    /** Parameter name for the request parameter "editormode". */
    public static final String PARAM_EDITORMODE = "editormode";

    /** Parameter name for the request parameter "element language". */
    public static final String PARAM_ELEMENTLANGUAGE = "elementlanguage";

    /** Parameter name for the request parameter "loaddefault". */
    public static final String PARAM_LOADDEFAULT = "loaddefault";

    /** Parameter name for the request parameter "modified". */
    public static final String PARAM_MODIFIED = "modified";

    /** Parameter name for the request parameter "old element language". */
    public static final String PARAM_OLDELEMENTLANGUAGE = "oldelementlanguage";

    /** Parameter name for the request parameter "tempfile". */
    public static final String PARAM_TEMPFILE = "tempfile";

    /** Stores the VFS editor path. */
    public static final String PATH_EDITORS = PATH_WORKPLACE + "editors/";

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsEditor.class);

    /** A cloned cms instance that prevents the broken link remotion during unmarshalling. */
    private CmsObject m_cloneCms;

    /** The encoding to use (will be read from the file property). */
    private String m_fileEncoding;
    // some private members for parameter storage
    private String m_paramBackLink;
    private String m_paramContent;
    private String m_paramDirectedit;
    private String m_paramEditAsText;
    private String m_paramEditormode;
    private String m_paramElementlanguage;
    private String m_paramLoadDefault;
    private String m_paramModified;
    private String m_paramOldelementlanguage;

    private String m_paramTempFile;

    /** Helper variable to store the uri to the editors pictures. */
    private String m_picsUri;

    /**
     * Public constructor.<p>
     * 
     * @param jsp an initialized JSP action element
     */
    public CmsEditor(CmsJspActionElement jsp) {

        super(jsp);
    }

    /**
     * Unlocks the edited resource when in direct edit mode or when the resource was not modified.<p>
     * 
     * @param forceUnlock if true, the resource will be unlocked anyway
     */
    public abstract void actionClear(boolean forceUnlock);

    /**
     * Performs the exit editor action.<p>
     * 
     * @throws CmsException if something goes wrong
     * @throws IOException if a forward fails
     * @throws ServletException if a forward fails
     * @throws JspException if including an element fails
     */
    public abstract void actionExit() throws CmsException, IOException, ServletException, JspException;

    /**
     * Performs the save content action.<p>
     * 
     * @throws IOException if a redirection fails
     * @throws JspException if including an element fails
     */
    public abstract void actionSave() throws IOException, JspException;

    /**
     * Builds the html String for the element language selector.<p>
     *  
     * @param attributes optional attributes for the &lt;select&gt; tag
     * @param resourceName the name of the resource to edit
     * @param selectedLocale the currently selected Locale
     * @return the html for the element language selectbox
     */
    public String buildSelectElementLanguage(String attributes, String resourceName, Locale selectedLocale) {

        // get locale names based on properties and global settings
        List locales = OpenCms.getLocaleManager().getAvailableLocales(getCms(), resourceName);
        List options = new ArrayList(locales.size());
        List selectList = new ArrayList(locales.size());
        int currentIndex = -1;

        //get the locales already used in the resource
        List contentLocales = new ArrayList();
        try {

            CmsResource res = getCms().readResource(resourceName, CmsResourceFilter.IGNORE_EXPIRATION);
            String temporaryFilename = CmsWorkplace.getTemporaryFileName(resourceName);
            if (getCms().existsResource(temporaryFilename, CmsResourceFilter.IGNORE_EXPIRATION)) {
                res = getCms().readResource(temporaryFilename, CmsResourceFilter.IGNORE_EXPIRATION);
            }
            CmsFile file = getCms().readFile(res);
            CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
            contentLocales = xmlContent.getLocales();
        } catch (CmsException e) {
            // to nothing here in case the resource could not be opened
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_GET_LOCALES_1, resourceName), e);
            }
        }

        for (int counter = 0; counter < locales.size(); counter++) {
            // create the list of options and values
            Locale curLocale = (Locale)locales.get(counter);
            selectList.add(curLocale.toString());
            StringBuffer buf = new StringBuffer();
            buf.append(curLocale.getDisplayName(getLocale()));
            if (!contentLocales.contains(curLocale)) {
                buf.append(EMPTY_LOCALE);
            }
            options.add(buf.toString());
            if (curLocale.equals(selectedLocale)) {
                // set the selected index of the selector
                currentIndex = counter;
            }
        }

        if (currentIndex == -1) {
            // no matching element language found, use first element language in list
            if (selectList.size() > 0) {
                currentIndex = 0;
                setParamElementlanguage((String)selectList.get(0));
            }
        }

        return buildSelect(attributes, options, selectList, currentIndex, false);
    }

    /**
     * Generates a button for the OpenCms editor.<p>
     * 
     * @param href the href link for the button, if none is given the button will be disabled
     * @param target the href link target for the button, if none is given the target will be same window
     * @param image the image name for the button, skin path will be automattically added as prefix
     * @param label the label for the text of the button 
     * @param type 0: image only (default), 1: image and text, 2: text only
     * @param useCustomImage if true, the button has to be placed in the editors "custom pics" folder
     * 
     * @return a button for the OpenCms editor
     */
    public String button(String href, String target, String image, String label, int type, boolean useCustomImage) {

        if (useCustomImage) {
            // search the picture in the "custom pics" folder
            return button(href, target, image, label, type, getPicsUri());
        } else {
            // search the picture in the common "buttons" folder
            return button(href, target, image, label, type);
        }
    }

    /**
     * Returns the editor action for a "cancel" button.<p>
     * 
     * This overwrites the cancel method of the CmsDialog class.<p>
     * 
     * Always use this value, do not write anything directly in the html page.<p>
     * 
     * @return the default action for a "cancel" button
     */
    public String buttonActionCancel() {

        String target = null;
        if (Boolean.valueOf(getParamDirectedit()).booleanValue()) {
            // editor is in direct edit mode
            if (CmsStringUtil.isNotEmpty(getParamBacklink())) {
                // set link to the specified back link target
                target = getParamBacklink();
            } else {
                // set link to the edited resource
                target = getParamResource();
            }
        } else {
            // in workplace mode, show explorer view
            target = OpenCms.getLinkManager().substituteLink(getCms(), CmsFrameset.JSP_WORKPLACE_URI);
        }
        return "onclick=\"top.location.href='" + getJsp().link(target) + "';\"";
    }

    /**
     * Builds the html to display the special action button for the direct edit mode of the editor.<p>
     * 
     * @param jsFunction the JavaScript function which will be executed on the mouseup event 
     * @param type 0: image only (default), 1: image and text, 2: text only
     * @return the html to display the special action button
     */
    public String buttonActionDirectEdit(String jsFunction, int type) {

        // get the action class from the OpenCms runtime property
        I_CmsEditorActionHandler actionClass = OpenCms.getWorkplaceManager().getEditorActionHandler();
        String url;
        String name;
        boolean active = false;
        if (actionClass != null) {
            // get button parameters and state from action class
            url = actionClass.getButtonUrl(getJsp(), getParamResource());
            name = actionClass.getButtonName();
            active = actionClass.isButtonActive(getJsp(), getParamResource());
        } else {
            // action class not defined, display inactive button
            url = getSkinUri() + "buttons/publish_in.png";
            name = Messages.GUI_EXPLORER_CONTEXT_PUBLISH_0;
        }
        String image = url.substring(url.lastIndexOf("/") + 1);
        if (url.endsWith(".gif")) {
            image = image.substring(0, image.length() - 4);
        }

        if (active) {
            // create the link for the button
            return button("javascript:" + jsFunction, null, image, name, type, url.substring(
                0,
                url.lastIndexOf("/") + 1));
        } else {
            // create the inactive button
            return button(null, null, image, name, type, url.substring(0, url.lastIndexOf("/") + 1));
        }
    }

    /**
     * @see org.opencms.workplace.CmsWorkplace#checkLock(String, CmsLockType)
     */
    public void checkLock(String resource, CmsLockType type) throws CmsException {

        CmsResource res = getCms().readResource(resource, CmsResourceFilter.ALL);
        CmsLock lock = getCms().getLock(res);
        if (!lock.isNullLock()) {
            setParamModified(Boolean.TRUE.toString());
        }

        // for resources with siblings make sure all sibling have at least a
        // temporary lock
        if ((res.getSiblingCount() > 1) && (lock.isInherited())) {
            super.checkLock(resource, CmsLockType.TEMPORARY);
        } else {
            super.checkLock(resource, type);
        }
    }

    /**
     * Generates a button for delete locale.<p>
     * 
     * @param href the href link for the button, if none is given the button will be disabled
     * @param target the href link target for the button, if none is given the target will be same window
     * @param image the image name for the button, skin path will be automattically added as prefix
     * @param label the label for the text of the button 
     * @param type 0: image only (default), 1: image and text, 2: text only
     * 
     * @return a button for the OpenCms workplace
     */
    public String deleteLocaleButton(String href, String target, String image, String label, int type) {

        String filename = getParamResource();

        try {
            CmsResource res = getCms().readResource(filename, CmsResourceFilter.IGNORE_EXPIRATION);

            String temporaryFilename = CmsWorkplace.getTemporaryFileName(filename);
            if (getCms().existsResource(temporaryFilename, CmsResourceFilter.IGNORE_EXPIRATION)) {
                res = getCms().readResource(temporaryFilename, CmsResourceFilter.IGNORE_EXPIRATION);
            }
            CmsFile file = getCms().readFile(res);
            CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
            int locales = xmlContent.getLocales().size();
            // there are less than 2 locales, so disable the delete locale button
            if (locales < 2) {
                href = null;
                target = null;
                image += "_in";
            }
        } catch (CmsException e) {
            // to nothing here in case the resource could not be opened
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_GET_LOCALES_1, filename), e);
            }
        }
        return button(href, target, image, label, type, getSkinUri() + "buttons/");

    }

    /**
     * Returns the instantiated editor display option class from the workplace manager.<p>
     * 
     * This is a convenience method to be used on editor JSPs.<p>
     * 
     * @return the instantiated editor display option class
     */
    public CmsEditorDisplayOptions getEditorDisplayOptions() {

        return OpenCms.getWorkplaceManager().getEditorDisplayOptions();
    }

    /**
     * Returns the URI to the editor resource folder where button images and javascripts are located.<p>
     * 
     * @return the URI to the editor resource folder
     */
    public abstract String getEditorResourceUri();

    /**
     * Returns the OpenCms request context path.<p>
     * 
     * This is a convenience method to use in the editor.<p>
     * 
     * @return the OpenCms request context path
     */
    public String getOpenCmsContext() {

        return OpenCms.getSystemInfo().getOpenCmsContext();
    }

    /**
     * Returns the back link when closing the editor.<p>
     * 
     * @return the back link
     */
    public String getParamBacklink() {

        if (m_paramBackLink == null) {
            m_paramBackLink = "";
        }
        return m_paramBackLink;
    }

    /**
     * Returns the content of the editor.<p>
     * @return the content of the editor
     */
    public String getParamContent() {

        if (m_paramContent == null) {
            m_paramContent = "";
        }
        return m_paramContent;
    }

    /**
     * Returns the direct edit flag parameter.<p>
     *  
     * @return the direct edit flag parameter
     */
    public String getParamDirectedit() {

        return m_paramDirectedit;
    }

    /**
     * Returns the edit as text parameter.<p>
     * 
     * @return the edit as text parameter
     */
    public String getParamEditastext() {

        return m_paramEditAsText;
    }

    /**
     * Returns the editor mode parameter.<p>
     *  
     * @return the editor mode parameter
     */
    public String getParamEditormode() {

        return m_paramEditormode;
    }

    /**
     * Returns the current element language.<p>
     * 
     * @return the current element language
     */
    public String getParamElementlanguage() {

        return m_paramElementlanguage;
    }

    /**
     * Returns the "loaddefault" parameter to determine if the default editor should be loaded.<p>
     * 
     * @return the "loaddefault" parameter
     */
    public String getParamLoaddefault() {

        return m_paramLoadDefault;
    }

    /**
     * Returns the modified parameter indicating if the resource has been saved.<p>
     *
     * @return the modified parameter indicating if the resource has been saved
     */
    public String getParamModified() {

        return m_paramModified;
    }

    /**
     * Returns the old element language.<p>
     * 
     * @return the old element language
     */
    public String getParamOldelementlanguage() {

        return m_paramOldelementlanguage;
    }

    /**
     * Returns the name of the temporary file.<p>
     * 
     * @return the name of the temporary file
     */
    public String getParamTempfile() {

        return m_paramTempFile;
    }

    /**
     * Returns the path to the images used by this editor.<p>
     * 
     * @return the path to the images used by this editor
     */
    public String getPicsUri() {

        if (m_picsUri == null) {
            m_picsUri = getEditorResourceUri() + "pics/";
        }
        return m_picsUri;
    }

    /**
     * Sets the back link when closing the editor.<p>
     * 
     * @param backLink the back link
     */
    public void setParamBacklink(String backLink) {

        m_paramBackLink = backLink;
    }

    /**
     * Sets the content of the editor.<p>
     * 
     * @param content the content of the editor
     */
    public void setParamContent(String content) {

        if (content == null) {
            content = "";
        }
        m_paramContent = content;
    }

    /**
     * Sets the direct edit flag parameter.<p>
     * 
     * @param direct the direct edit flag parameter
     */
    public void setParamDirectedit(String direct) {

        m_paramDirectedit = direct;
    }

    /**
     * Sets the  edit as text parameter.<p>
     * 
     * @param editAsText <code>"true"</code> if the resource should be handled like a text file
     */
    public void setParamEditastext(String editAsText) {

        m_paramEditAsText = editAsText;
    }

    /**
     * Sets the editor mode parameter.<p>
     * 
     * @param mode the editor mode parameter
     */
    public void setParamEditormode(String mode) {

        m_paramEditormode = mode;
    }

    /**
     * Sets the current element language.<p>
     * 
     * @param elementLanguage the current element language
     */
    public void setParamElementlanguage(String elementLanguage) {

        m_paramElementlanguage = elementLanguage;
    }

    /**
     * Sets the "loaddefault" parameter to determine if the default editor should be loaded.<p>
     * 
     * @param loadDefault the "loaddefault" parameter
     */
    public void setParamLoaddefault(String loadDefault) {

        m_paramLoadDefault = loadDefault;
    }

    /**
     * Sets the modified parameter indicating if the resource has been saved.<p>
     *
     * @param modified the modified parameter indicating if the resource has been saved
     */
    public void setParamModified(String modified) {

        m_paramModified = modified;
    }

    /**
     * Sets the old element language.<p>
     * 
     * @param oldElementLanguage the old element language
     */
    public void setParamOldelementlanguage(String oldElementLanguage) {

        m_paramOldelementlanguage = oldElementLanguage;
    }

    /**
     * Sets the name of the temporary file.<p>
     * 
     * @param fileName the name of the temporary file
     */
    public void setParamTempfile(String fileName) {

        m_paramTempFile = fileName;
    }

    /**
     * Closes the editor and forwards to the workplace or the resource depending on the editor mode.<p>
     * 
     * @throws IOException if forwarding fails 
     * @throws ServletException if forwarding fails
     * @throws JspException if including a JSP fails
     */
    protected void actionClose() throws IOException, JspException, ServletException {

        if (Boolean.valueOf(getParamDirectedit()).booleanValue()) {
            // editor is in direct edit mode
            if (CmsStringUtil.isNotEmpty(getParamBacklink())) {
                // set link to the specified back link target
                setParamCloseLink(getJsp().link(getParamBacklink()));
            } else {
                // set link to the edited resource
                setParamCloseLink(getJsp().link(getParamResource()));
            }
            // save initialized instance of this class in request attribute for included sub-elements
            getJsp().getRequest().setAttribute(SESSION_WORKPLACE_CLASS, this);
            // load the common JSP close dialog
            getJsp().include(FILE_DIALOG_CLOSE);
        } else {
            if (CmsStringUtil.isNotEmpty(getParamBacklink())) {
                // set link to the specified back link target
                setParamCloseLink(getJsp().link(getParamBacklink()));
                // save initialized instance of this class in request attribute for included sub-elements
                getJsp().getRequest().setAttribute(SESSION_WORKPLACE_CLASS, this);
                // load the common JSP close dialog
                getJsp().include(FILE_DIALOG_CLOSE);
            } else {
                // forward to the workplace explorer view
                sendForward(CmsFrameset.JSP_WORKPLACE_URI, new HashMap());
            }
        }
    }

    /**
     * Writes the content of a temporary file back to the original file.<p>
     * 
     * @throws CmsException if something goes wrong
     */
    protected void commitTempFile() throws CmsException {

        CmsObject cms = getCms();
        CmsFile tempFile;
        List properties;
        try {
            switchToTempProject();
            tempFile = cms.readFile(getParamTempfile(), CmsResourceFilter.ALL);
            properties = cms.readPropertyObjects(getParamTempfile(), false);
        } finally {
            // make sure the project is reset in case of any exception
            switchToCurrentProject();
        }
        if (cms.existsResource(getParamResource(), CmsResourceFilter.ALL)) {
            // update properties of original file first (required if change in encoding occurred)
            cms.writePropertyObjects(getParamResource(), properties);
            // now replace the content of the original file
            CmsFile orgFile = cms.readFile(getParamResource(), CmsResourceFilter.ALL);
            orgFile.setContents(tempFile.getContents());
            getCloneCms().writeFile(orgFile);
        } else {
            // original file does not exist, remove visibility permission entries and copy temporary file

            // switch to the temporary file project
            cms.getRequestContext().setCurrentProject(cms.readProject(getSettings().getProject()));
            // lock the temporary file
            cms.changeLock(getParamTempfile());
            // remove visibility permissions for everybody on temporary file if possible
            if (cms.hasPermissions(tempFile, CmsPermissionSet.ACCESS_CONTROL)) {
                cms.rmacc(getParamTempfile(), I_CmsPrincipal.PRINCIPAL_GROUP, OpenCms.getDefaultUsers().getGroupUsers());
                cms.rmacc(
                    getParamTempfile(),
                    I_CmsPrincipal.PRINCIPAL_GROUP,
                    OpenCms.getDefaultUsers().getGroupProjectmanagers());
            }

            cms.copyResource(getParamTempfile(), getParamResource(), CmsResource.COPY_AS_NEW);
        }
        // remove the temporary file flag
        int flags = cms.readResource(getParamResource(), CmsResourceFilter.ALL).getFlags();
        if ((flags & CmsResource.FLAG_TEMPFILE) == CmsResource.FLAG_TEMPFILE) {
            flags ^= CmsResource.FLAG_TEMPFILE;
            cms.chflags(getParamResource(), flags);
        }
    }

    /**
     * Creates a temporary file which is needed while working in an editor with preview option.<p>
     * 
     * @return the file name of the temporary file
     * @throws CmsException if something goes wrong
     */
    protected String createTempFile() throws CmsException {

        return OpenCms.getWorkplaceManager().createTempFile(getCms(), getParamResource(), getSettings().getProject());
    }

    /**
     * Decodes the given content the same way the client would do it.<p>
     * 
     * Content is decoded as if it was encoded using the JavaScript
     * "encodeURIComponent()" function.<p>
     * 
     * @param content the content to decode
     * @return the decoded content
     */
    protected String decodeContent(String content) {

        return CmsEncoder.unescape(content, CmsEncoder.ENCODING_UTF_8);
    }

    /**
     * Decodes an individual parameter value, ensuring the content is always decoded in UTF-8.<p>
     * 
     * For editors the content is always encoded using the 
     * JavaScript encodeURIComponent() method on the client,
     * which always encodes in UTF-8.<p> 
     * 
     * @param paramName the name of the parameter 
     * @param paramValue the unencoded value of the parameter
     * @return the encoded value of the parameter
     */
    protected String decodeParamValue(String paramName, String paramValue) {

        if ((paramName != null) && (paramValue != null)) {
            if (PARAM_CONTENT.equals(paramName)) {
                // content will be always encoded in UTF-8 unicode by the editor client
                return CmsEncoder.decode(paramValue, CmsEncoder.ENCODING_UTF_8);
            } else if (PARAM_RESOURCE.equals(paramName) || PARAM_TEMPFILE.equals(paramName)) {
                String filename = CmsEncoder.decode(paramValue, getCms().getRequestContext().getEncoding());
                if (PARAM_TEMPFILE.equals(paramName) || CmsStringUtil.isEmpty(getParamTempfile())) {
                    // always use value from temp file if it is available
                    setFileEncoding(getFileEncoding(getCms(), filename));
                }
                return filename;
            } else {
                return CmsEncoder.decode(paramValue, getCms().getRequestContext().getEncoding());
            }
        } else {
            return null;
        }
    }

    /**
     * Deletes a temporary file from the OpenCms VFS, needed when exiting an editor.<p> 
     */
    protected void deleteTempFile() {

        try {
            // switch to the temporary file project
            switchToTempProject();
            // delete the temporary file
            getCms().deleteResource(getParamTempfile(), CmsResource.DELETE_PRESERVE_SIBLINGS);
        } catch (CmsException e) {
            // should usually never happen
            if (LOG.isInfoEnabled()) {
                LOG.info(e);
            }
        } finally {
            try {
                // switch back to the current project
                switchToCurrentProject();
            } catch (CmsException e) {
                // should usually never happen
                if (LOG.isInfoEnabled()) {
                    LOG.info(e);
                }
            }
        }
    }

    /**
     * Encodes the given content so that it can be transfered to the client.<p>
     * 
     * Content is encoded so that it is compatible with the JavaScript
     * "decodeURIComponent()" function.<p>
     * 
     * @param content the content to encode
     * @return the encoded content
     */
    protected String encodeContent(String content) {

        return CmsEncoder.escapeWBlanks(content, CmsEncoder.ENCODING_UTF_8);
    }

    /** 
     * Returns a cloned cms instance that prevents the time range resource filter check.<p> 
     * 
     * Use it always for unmarschalling and file writing.<p>
     * 
     * @return a cloned cms instance that prevents the time range resource filter check
     * 
     * @throws CmsException if something goes wrong
     */
    protected CmsObject getCloneCms() throws CmsException {

        if (m_cloneCms == null) {
            m_cloneCms = OpenCms.initCmsObject(getCms());
            m_cloneCms.getRequestContext().setRequestTime(CmsResource.DATE_RELEASED_EXPIRED_IGNORE);
        }
        return m_cloneCms;
    }

    /**
     * Returns the encoding parameter.<p>
     *
     * @return the encoding parameter
     */
    protected String getFileEncoding() {

        return m_fileEncoding;
    }

    /**
     * Helper method to determine the encoding of the given file in the VFS,
     * which must be set using the "content-encoding" property.<p>
     * 
     * @param cms the CmsObject
     * @param filename the name of the file which is to be checked
     * @return the encoding for the file
     */
    protected String getFileEncoding(CmsObject cms, String filename) {

        try {
            return cms.readPropertyObject(filename, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue(
                OpenCms.getSystemInfo().getDefaultEncoding());
        } catch (CmsException e) {
            return OpenCms.getSystemInfo().getDefaultEncoding();
        }
    }

    /**
     * Initializes the editor content when openening the editor for the first time.<p>
     */
    protected abstract void initContent();

    /**
     * Sets the encoding parameter.<p>
     *
     * @param value the encoding value to set
     */
    protected void setFileEncoding(String value) {

        m_fileEncoding = CmsEncoder.lookupEncoding(value, value);
    }

    /**
     * Shows the selected error page in case of an exception.<p>
     * 
     * @param exception the current exception
     * @throws JspException if inclusion of the error page fails
     */
    protected void showErrorPage(Exception exception) throws JspException {

        // reset the action parameter            
        setParamAction("");
        showErrorPage(this, exception);
        // save not successful, set cancel action 
        setAction(ACTION_CANCEL);
        return;
    }

    /**
     * Shows the selected error page in case of an exception.<p>
     * 
     * @param editor initialized instance of the editor class
     * @param exception the current exception
     * @throws JspException if inclusion of the error page fails
     */
    protected void showErrorPage(Object editor, Exception exception) throws JspException {

        // save initialized instance of the editor class in request attribute for included sub-elements
        getJsp().getRequest().setAttribute(SESSION_WORKPLACE_CLASS, editor);

        // reading of file contents failed, show error dialog
        setAction(ACTION_SHOW_ERRORMESSAGE);
        setParamTitle(key(Messages.GUI_TITLE_EDIT_1, new Object[] {CmsResource.getName(getParamResource())}));
        if (exception != null) {
            getJsp().getRequest().setAttribute(ATTRIBUTE_THROWABLE, exception);
            if (CmsLog.getLog(editor).isWarnEnabled()) {
                CmsLog.getLog(editor).warn(exception.getLocalizedMessage(), exception);
            }
        }
        // include the common error dialog
        getJsp().include(FILE_DIALOG_SCREEN_ERRORPAGE);
    }
}