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.workplace.editors;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsPropertyDefinition;
032import org.opencms.file.CmsRequestContext;
033import org.opencms.i18n.CmsEncoder;
034import org.opencms.i18n.CmsLocaleManager;
035import org.opencms.jsp.CmsJspActionElement;
036import org.opencms.main.CmsException;
037import org.opencms.main.CmsLog;
038import org.opencms.main.OpenCms;
039import org.opencms.util.CmsHtmlConverter;
040import org.opencms.util.CmsStringUtil;
041import org.opencms.xml.CmsXmlException;
042import org.opencms.xml.page.CmsXmlPage;
043import org.opencms.xml.page.CmsXmlPageFactory;
044
045import java.io.IOException;
046import java.util.ArrayList;
047import java.util.Iterator;
048import java.util.List;
049import java.util.Locale;
050
051import javax.servlet.ServletException;
052import javax.servlet.jsp.JspException;
053
054import org.apache.commons.logging.Log;
055
056/**
057 * Provides methods for building editors for the CmsDefaultPage page type.<p>
058 *
059 * Extend this class for all editors that work with the CmsDefaultPage.<p>
060 *
061 * @since 6.0.0
062 */
063public abstract class CmsDefaultPageEditor extends CmsEditor {
064
065    /** Parameter name for the request parameter "element name". */
066    public static final String PARAM_ELEMENTNAME = "elementname";
067
068    /** Parameter name for the request parameter "old element name". */
069    public static final String PARAM_OLDELEMENTNAME = "oldelementname";
070
071    /** option values for font select boxes. */
072    public static final String[] SELECTBOX_FONTS = {
073        "Arial",
074        "Arial Narrow",
075        "System",
076        "Times New Roman",
077        "Verdana",
078        "Monospace",
079        "SansSerif"};
080
081    /** Name of the special body element from an XMLTemplate. */
082    public static final String XML_BODY_ELEMENT = "body";
083
084    /** The log object for this class. */
085    private static final Log LOG = CmsLog.getLog(CmsDefaultPageEditor.class);
086
087    /** File object used to read and write contents. */
088    protected CmsFile m_file;
089
090    /** Page object used from the action and init methods, be sure to initialize this e.g. in the initWorkplaceRequestValues method. */
091    protected CmsXmlPage m_page;
092
093    /** The element list. */
094    private List<CmsDialogElement> m_elementList;
095
096    /** The element locale. */
097    private Locale m_elementLocale;
098
099    /** The element name parameter. */
100    private String m_paramElementname;
101
102    /** The old element name parameter. */
103    private String m_paramOldelementname;
104
105    /** The URI of the style sheet to use in the editor. */
106    private String m_uriStyleSheet;
107
108    /**
109     * Public constructor.<p>
110     *
111     * @param jsp an initialized JSP action element
112     */
113    public CmsDefaultPageEditor(CmsJspActionElement jsp) {
114
115        super(jsp);
116    }
117
118    /**
119     * Performs the change body action of the editor.<p>
120     */
121    public void actionChangeBodyElement() {
122
123        try {
124            // save eventually changed content of the editor to the temporary file
125            Locale oldLocale = CmsLocaleManager.getLocale(getParamOldelementlanguage());
126            performSaveContent(getParamOldelementname(), oldLocale);
127        } catch (CmsException e) {
128            // show error page
129            try {
130                showErrorPage(this, e);
131            } catch (JspException exc) {
132                // should usually never happen
133                if (LOG.isInfoEnabled()) {
134                    LOG.info(exc);
135                }
136            }
137        }
138        // re-initialize the element name if the language has changed
139        if (!getParamElementlanguage().equals(getParamOldelementlanguage())) {
140            initBodyElementName(getParamOldelementname());
141        }
142        // get the new editor content
143        initContent();
144    }
145
146    /**
147     * Performs the cleanup body action of the editor.<p>
148     */
149    public void actionCleanupBodyElement() {
150
151        try {
152            // save eventually changed content of the editor to the temporary file
153            Locale oldLocale = CmsLocaleManager.getLocale(getParamOldelementlanguage());
154            performSaveContent(getParamOldelementname(), oldLocale);
155        } catch (CmsException e) {
156            // show error page
157            try {
158                showErrorPage(this, e);
159            } catch (JspException exc) {
160                // should usually never happen
161                if (LOG.isInfoEnabled()) {
162                    LOG.info(exc);
163                }
164            }
165        }
166    }
167
168    /**
169     * @see org.opencms.workplace.editors.CmsEditor#actionClear(boolean)
170     */
171    @Override
172    public void actionClear(boolean forceUnlock) {
173
174        // delete the temporary file
175        deleteTempFile();
176        boolean directEditMode = Boolean.valueOf(getParamDirectedit()).booleanValue();
177        boolean modified = Boolean.valueOf(getParamModified()).booleanValue();
178        if (directEditMode || forceUnlock || !modified) {
179            // unlock the resource when in direct edit mode, force unlock is true or resource was not modified
180            try {
181                getCms().unlockResource(getParamResource());
182            } catch (CmsException e) {
183                // should usually never happen
184                if (LOG.isInfoEnabled()) {
185                    LOG.info(e.getLocalizedMessage(), e);
186                }
187            }
188        }
189    }
190
191    /**
192     * Performs the delete locale action.<p>
193     *
194     * @throws JspException if something goes wrong
195     */
196    public void actionDeleteElementLocale() throws JspException {
197
198        try {
199            Locale loc = getElementLocale();
200            m_page.removeLocale(loc);
201            //write the modified xml content
202            m_file.setContents(m_page.marshal());
203            m_file = getCms().writeFile(m_file);
204            List<Locale> locales = m_page.getLocales();
205            if (locales.size() > 0) {
206                // set first locale as new display locale
207                Locale newLoc = locales.get(0);
208                setParamElementlanguage(newLoc.toString());
209                m_elementLocale = newLoc;
210            } else {
211                if (LOG.isErrorEnabled()) {
212                    LOG.error(Messages.get().getBundle().key(Messages.LOG_GET_LOCALES_1, getParamResource()));
213                }
214            }
215            initContent();
216        } catch (CmsXmlException e) {
217            // an error occurred while trying to delete the locale, stop action
218            showErrorPage(e);
219        } catch (CmsException e) {
220            // should usually never happen
221            if (LOG.isInfoEnabled()) {
222                LOG.info(e.getLocalizedMessage(), e);
223            }
224        }
225    }
226
227    /**
228     * Performs a configurable action performed by the editor.<p>
229     *
230     * The default action is: save resource, clear temporary files and publish the resource directly.<p>
231     *
232     * @throws IOException if a forward fails
233     * @throws JspException if including a JSP fails
234     * @throws ServletException if a forward fails
235     */
236    public void actionDirectEdit() throws IOException, JspException, ServletException {
237
238        // get the action class from the OpenCms runtime property
239        I_CmsEditorActionHandler actionClass = OpenCms.getWorkplaceManager().getEditorActionHandler();
240        if (actionClass == null) {
241            // error getting the action class, save content and exit the editor
242            actionSave();
243            actionExit();
244        } else {
245            actionClass.editorAction(this, getJsp());
246        }
247    }
248
249    /**
250     * Performs the exit editor action and deletes the temporary file.<p>
251     *
252     * @see org.opencms.workplace.editors.CmsEditor#actionExit()
253     */
254    @Override
255    public void actionExit() throws IOException, JspException, ServletException {
256
257        if (getAction() == ACTION_CANCEL) {
258            // save and exit was canceled
259            return;
260        }
261        // clear temporary file and unlock resource, if in directedit mode
262        actionClear(false);
263        // close the editor
264        actionClose();
265    }
266
267    /**
268     * Performs the preview page action in a new browser window.<p>
269     *
270     * @throws IOException if redirect fails
271     * @throws JspException if inclusion of error page fails
272     */
273    public void actionPreview() throws IOException, JspException {
274
275        try {
276            // save content of the editor to the temporary file
277            performSaveContent(getParamElementname(), getElementLocale());
278        } catch (CmsException e) {
279            // show error page
280            showErrorPage(this, e);
281        }
282
283        // redirect to the temporary file with current active element language
284        String param = "?" + org.opencms.i18n.CmsLocaleManager.PARAMETER_LOCALE + "=" + getParamElementlanguage();
285        sendCmsRedirect(getParamTempfile() + param);
286    }
287
288    /**
289     * @see org.opencms.workplace.editors.CmsEditor#actionSave()
290     */
291    @Override
292    public void actionSave() throws JspException {
293
294        try {
295
296            // save content to temporary file
297            performSaveContent(getParamElementname(), getElementLocale());
298            // copy the temporary file content back to the original file
299            commitTempFile();
300            // set the modified parameter
301            setParamModified(Boolean.TRUE.toString());
302        } catch (CmsException e) {
303            showErrorPage(e);
304        }
305
306        if (getAction() != ACTION_CANCEL) {
307            // save successful, set save action
308            setAction(ACTION_SAVE);
309        }
310    }
311
312    /**
313     * Builds the html String for the element language selector.<p>
314     *
315     * @param attributes optional attributes for the &lt;select&gt; tag
316     * @return the html for the element language selectbox
317     */
318    public String buildSelectElementLanguage(String attributes) {
319
320        return buildSelectElementLanguage(attributes, getParamResource(), getElementLocale());
321    }
322
323    /**
324     * Builds the html String for the element name selector.<p>
325     *
326     * @param attributes optional attributes for the &lt;select&gt; tag
327     * @return the html for the element name selectbox
328     */
329    public String buildSelectElementName(String attributes) {
330
331        // get the active page elements
332        List<CmsDialogElement> elementList = getElementList();
333
334        int counter = 0;
335        int currentIndex = -1;
336        List<String> options = new ArrayList<String>(elementList.size());
337        List<String> values = new ArrayList<String>(elementList.size());
338        String elementName = getParamElementname();
339        if (CmsStringUtil.isEmpty(elementName)) {
340            elementName = getParamOldelementname();
341        }
342        for (int i = 0; i < elementList.size(); i++) {
343            // get the current list element
344            CmsDialogElement element = elementList.get(i);
345
346            if (CmsStringUtil.isNotEmpty(elementName) && elementName.equals(element.getName())) {
347                // current element is the displayed one, mark it as selected
348                currentIndex = counter;
349            }
350            if ((!m_page.hasValue(element.getName(), getElementLocale()) && element.isMandantory())
351                || m_page.isEnabled(element.getName(), getElementLocale())) {
352                // add element if it is not available or if it is enabled
353                options.add(element.getNiceName());
354                values.add(element.getName());
355                counter++;
356            }
357        }
358        return buildSelect(attributes, options, values, currentIndex, false);
359    }
360
361    /**
362     * Builds the html for the font face select box of a WYSIWYG editor.<p>
363     *
364     * @param attributes optional attributes for the &lt;select&gt; tag
365     * @return the html for the font face select box
366     */
367    public String buildSelectFonts(String attributes) {
368
369        List<String> names = new ArrayList<String>();
370        for (int i = 0; i < CmsDefaultPageEditor.SELECTBOX_FONTS.length; i++) {
371            String value = CmsDefaultPageEditor.SELECTBOX_FONTS[i];
372            names.add(value);
373        }
374        return buildSelect(attributes, names, names, -1, false);
375    }
376
377    /**
378     * Escapes the content and title parameters to display them in the editor form.<p>
379     *
380     * This method has to be called on the JSP right before the form display html is created.<p>     *
381     */
382    public void escapeParams() {
383
384        // escape the content
385        setParamContent(CmsEncoder.escapeWBlanks(getParamContent(), CmsEncoder.ENCODING_UTF_8));
386    }
387
388    /**
389     * Returns the current element locale.<p>
390     *
391     * @return the current element locale
392     */
393    public Locale getElementLocale() {
394
395        if (m_elementLocale == null) {
396            m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage());
397        }
398        return m_elementLocale;
399    }
400
401    /**
402     * Returns the current element name.<p>
403     *
404     * @return the current element name
405     */
406    public String getParamElementname() {
407
408        return m_paramElementname;
409    }
410
411    /**
412     * Returns the old element name.<p>
413     *
414     * @return the old element name
415     */
416    public String getParamOldelementname() {
417
418        return m_paramOldelementname;
419    }
420
421    /**
422     * Returns the OpenCms VFS uri of the style sheet of the current page.<p>
423     *
424     * @return the OpenCms VFS uri of the style sheet of the current page
425     */
426    public String getUriStyleSheet() {
427
428        if (m_uriStyleSheet == null) {
429            try {
430                if (OpenCms.getWorkplaceManager().getEditorCssHandlers().size() > 0) {
431                    // use the configured handlers to determine the CSS to use
432                    Iterator<I_CmsEditorCssHandler> i = OpenCms.getWorkplaceManager().getEditorCssHandlers().iterator();
433                    while (i.hasNext()) {
434                        I_CmsEditorCssHandler cssHandler = i.next();
435                        if (cssHandler.matches(getCms(), getParamTempfile())) {
436                            m_uriStyleSheet = cssHandler.getUriStyleSheet(getCms(), getParamTempfile());
437                            break;
438                        }
439                    }
440                } else {
441                    // for compatibility reasons, read the template property value from the template file to get the CSS
442                    String currentTemplate = getUriTemplate();
443                    m_uriStyleSheet = getCms().readPropertyObject(
444                        currentTemplate,
445                        CmsPropertyDefinition.PROPERTY_TEMPLATE,
446                        false).getValue("");
447                }
448            } catch (CmsException e) {
449                LOG.warn(Messages.get().getBundle().key(Messages.LOG_READ_TEMPLATE_PROP_STYLESHEET_FAILED_0), e);
450            }
451        }
452        return m_uriStyleSheet;
453    }
454
455    /**
456     * Returns the OpenCms VFS uri of the template of the current page.<p>
457     *
458     * @return the OpenCms VFS uri of the template of the current page
459     */
460    public String getUriTemplate() {
461
462        String result = "";
463        try {
464            result = getCms().readPropertyObject(
465                getParamTempfile(),
466                CmsPropertyDefinition.PROPERTY_TEMPLATE,
467                true).getValue("");
468        } catch (CmsException e) {
469            LOG.warn(Messages.get().getBundle().key(Messages.LOG_READ_TEMPLATE_PROP_FAILED_0), e);
470        }
471        return result;
472    }
473
474    /**
475     * Sets the current element name.<p>
476     *
477     * @param elementName the current element name
478     */
479    public void setParamElementname(String elementName) {
480
481        m_paramElementname = elementName;
482    }
483
484    /**
485     * Sets the old element name.<p>
486     *
487     * @param oldElementName the old element name
488     */
489    public void setParamOldelementname(String oldElementName) {
490
491        m_paramOldelementname = oldElementName;
492    }
493
494    /**
495     * Returns the list of active elements of the page.<p>
496     *
497     * @return the list of active elements of the page
498     */
499    protected List<CmsDialogElement> getElementList() {
500
501        if (m_elementList == null) {
502            m_elementList = CmsDialogElements.computeElements(getCms(), m_page, getParamTempfile(), getElementLocale());
503        }
504        return m_elementList;
505    }
506
507    /**
508     * Initializes the body element language for the first call of the editor.<p>
509     */
510    protected void initBodyElementLanguage() {
511
512        List<Locale> locales = m_page.getLocales();
513        Locale defaultLocale = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getCms().getSitePath(m_file)).get(
514            0);
515
516        if (locales.size() == 0) {
517            // no body present, create default body
518            if (!m_page.hasValue(CmsDefaultPageEditor.XML_BODY_ELEMENT, defaultLocale)) {
519                m_page.addValue(CmsDefaultPageEditor.XML_BODY_ELEMENT, defaultLocale);
520            }
521            try {
522                m_file.setContents(m_page.marshal());
523                getCms().writeFile(m_file);
524            } catch (CmsException e) {
525                // show error page
526                try {
527                    showErrorPage(this, e);
528                } catch (JspException exc) {
529                    // should usually never happen
530                    if (LOG.isInfoEnabled()) {
531                        LOG.info(exc);
532                    }
533                }
534            }
535            setParamElementlanguage(defaultLocale.toString());
536        } else {
537            // body present, get the language
538            if (locales.contains(defaultLocale)) {
539                // get the body for the default language
540                setParamElementlanguage(defaultLocale.toString());
541            } else {
542                // get the first body that can be found
543                setParamElementlanguage(locales.get(0).toString());
544            }
545
546        }
547    }
548
549    /**
550     * Initializes the body element name of the editor.<p>
551     *
552     * This has to be called after the element language has been set with setParamBodylanguage().<p>
553     *
554     * @param elementName the name of the element to initialize or null, if default element should be used
555     */
556    protected void initBodyElementName(String elementName) {
557
558        if ((elementName == null)
559            || (m_page.hasValue(elementName, getElementLocale())
560                && !m_page.isEnabled(elementName, getElementLocale()))) {
561            // elementName not specified or given element is disabled, determine default element
562            List<String> allElements = m_page.getNames(getElementLocale());
563            int elementCount = allElements.size();
564            List<String> elements = new ArrayList<String>(elementCount);
565            for (int i = 0; i < elementCount; i++) {
566                // filter disabled elements
567                if (m_page.isEnabled(allElements.get(i), getElementLocale())) {
568                    elements.add(allElements.get(i));
569                }
570            }
571
572            // get the active page elements
573            List<CmsDialogElement> elementList = getElementList();
574            for (int i = 0; i < elementList.size(); i++) {
575                CmsDialogElement checkElement = elementList.get(i);
576                if (elements.contains(checkElement.getName())) {
577                    // get the first active element from the element list
578                    setParamElementname(checkElement.getName());
579                    return;
580                }
581            }
582
583            // no matching active element found
584            if (elements.contains(CmsDefaultPageEditor.XML_BODY_ELEMENT)) {
585                // default legacy element present, use it
586                setParamElementname(CmsDefaultPageEditor.XML_BODY_ELEMENT);
587            } else {
588                // use the first element from the element list
589                setParamElementname(elements.get(0));
590            }
591        } else {
592            // elementName specified and element is enabled or not present, set to elementName
593            setParamElementname(elementName);
594        }
595    }
596
597    /**
598     * This method has to be called after initializing the body element name and language.<p>
599     *
600     * @see org.opencms.workplace.editors.CmsEditor#initContent()
601     */
602    @Override
603    protected void initContent() {
604
605        if (CmsStringUtil.isNotEmpty(getParamContent())) {
606            if (CmsStringUtil.isNotEmpty(getParamElementname())
607                && getParamElementname().equals(getParamOldelementname())) {
608                if (CmsStringUtil.isNotEmpty(getParamElementlanguage())
609                    && getParamElementlanguage().equals(getParamOldelementlanguage())) {
610                    return;
611                }
612            }
613        }
614        getCms().getRequestContext().setAttribute(CmsRequestContext.ATTRIBUTE_EDITOR, Boolean.TRUE);
615
616        String elementData;
617        if (m_page.hasValue(getParamElementname(), getElementLocale())) {
618            // element value is available in the page
619            elementData = m_page.getStringValue(getCms(), getParamElementname(), getElementLocale());
620        } else {
621            // value is not available in the page
622            if (Boolean.valueOf(getParamDirectedit()).booleanValue()) {
623                // direct edit on a non-existing element: create new element with this name
624                m_page.addValue(getParamElementname(), getElementLocale());
625            }
626            elementData = "";
627        }
628        setParamContent(elementData);
629    }
630
631    /**
632     * Saves the editor content to the temporary file.<p>
633     *
634     * @param body the body name to write
635     * @param locale the body locale to write
636     * @throws CmsException if writing the file fails
637     */
638    protected void performSaveContent(String body, Locale locale) throws CmsException {
639
640        // prepare the content for saving
641        String content = prepareContent(true);
642
643        String contentConversion = m_page.getConversion();
644        // check if cleanup was selected in the editor, we have to add the cleanup parameter
645        if (EDITOR_CLEANUP.equals(getParamAction())) {
646            if ((contentConversion == null) || (contentConversion.equals(CmsHtmlConverter.PARAM_DISABLED))) {
647                // if the current conversion mode is "false" only, we have to remove the "false" value and set it to "cleanup", as "false" will be stronger than all other values
648                contentConversion = CmsHtmlConverter.PARAM_WORD;
649            } else {
650                // add "cleanup" to the already existing values
651                contentConversion += ";" + CmsHtmlConverter.PARAM_WORD;
652            }
653        }
654        m_page.setConversion(contentConversion);
655
656        // create the element if necessary and if content is present
657        if (!m_page.hasValue(body, locale) && !"".equals(content)) {
658            m_page.addValue(body, locale);
659        }
660
661        // get the enabled state of the element
662        boolean enabled = m_page.isEnabled(body, locale);
663
664        // set the element data
665        if (m_page.hasValue(body, locale)) {
666            m_page.setStringValue(getCms(), body, locale, content);
667        }
668
669        // write the file
670        m_file.setContents(m_page.marshal());
671        m_file = getCms().writeFile(m_file);
672
673        // content might have been modified during write operation
674        m_page = CmsXmlPageFactory.unmarshal(getCms(), m_file);
675        if (m_page.hasValue(body, locale)) {
676            getCms().getRequestContext().setAttribute(CmsRequestContext.ATTRIBUTE_EDITOR, Boolean.TRUE);
677            content = m_page.getStringValue(getCms(), body, locale);
678            if (content == null) {
679                content = "";
680            }
681            setParamContent(content);
682            prepareContent(false);
683            m_page.setEnabled(body, locale, enabled);
684        }
685    }
686
687    /**
688     * Manipulates the content String for different editor views and the save operation.<p>
689     *
690     * @param save if set to true, the result String is not escaped and the content parameter is not updated
691     * @return the prepared content String
692     */
693    protected abstract String prepareContent(boolean save);
694
695}