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.CmsObject;
032import org.opencms.file.CmsRequestContext;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.collectors.A_CmsResourceCollector;
036import org.opencms.i18n.CmsEncoder;
037import org.opencms.i18n.CmsLocaleManager;
038import org.opencms.i18n.CmsMultiMessages;
039import org.opencms.json.JSONArray;
040import org.opencms.json.JSONException;
041import org.opencms.json.JSONObject;
042import org.opencms.jsp.CmsJspActionElement;
043import org.opencms.lock.CmsLockType;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047import org.opencms.util.CmsRequestUtil;
048import org.opencms.util.CmsStringUtil;
049import org.opencms.util.CmsUUID;
050import org.opencms.widgets.A_CmsWidget;
051import org.opencms.widgets.I_CmsWidget;
052import org.opencms.widgets.I_CmsWidgetDialog;
053import org.opencms.widgets.I_CmsWidgetParameter;
054import org.opencms.workplace.CmsWorkplace;
055import org.opencms.workplace.CmsWorkplaceSettings;
056import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection;
057import org.opencms.xml.CmsXmlContentDefinition;
058import org.opencms.xml.CmsXmlEntityResolver;
059import org.opencms.xml.CmsXmlException;
060import org.opencms.xml.CmsXmlUtils;
061import org.opencms.xml.content.CmsXmlContent;
062import org.opencms.xml.content.CmsXmlContentErrorHandler;
063import org.opencms.xml.content.CmsXmlContentFactory;
064import org.opencms.xml.content.CmsXmlContentTab;
065import org.opencms.xml.content.CmsXmlContentValueSequence;
066import org.opencms.xml.types.CmsXmlNestedContentDefinition;
067import org.opencms.xml.types.I_CmsXmlContentValue;
068import org.opencms.xml.types.I_CmsXmlSchemaType;
069
070import java.io.IOException;
071import java.io.UnsupportedEncodingException;
072import java.util.ArrayList;
073import java.util.HashSet;
074import java.util.Iterator;
075import java.util.List;
076import java.util.Locale;
077import java.util.Map;
078import java.util.Set;
079
080import javax.servlet.ServletException;
081import javax.servlet.http.HttpServletRequest;
082import javax.servlet.jsp.JspException;
083
084import org.apache.commons.logging.Log;
085
086/**
087 * Creates the editor for XML content definitions.<p>
088 *
089 * @since 6.0.0
090 */
091public class CmsXmlContentEditor extends CmsEditor implements I_CmsWidgetDialog {
092
093    /** Action for checking content before executing the direct edit action. */
094    public static final int ACTION_CHECK = 151;
095
096    /** Action for confirming the XML content structure correction. */
097    public static final int ACTION_CONFIRMCORRECTION = 152;
098
099    /** Value for the action: copy the current locale. */
100    public static final int ACTION_COPYLOCALE = 141;
101
102    /** Action for correction of the XML content structure confirmed. */
103    public static final int ACTION_CORRECTIONCONFIRMED = 153;
104
105    /** Action for optional element creation. */
106    public static final int ACTION_ELEMENT_ADD = 154;
107
108    /** Action for element move down operation. */
109    public static final int ACTION_ELEMENT_MOVE_DOWN = 155;
110
111    /** Action for element move up operation. */
112    public static final int ACTION_ELEMENT_MOVE_UP = 156;
113
114    /** Action for optional element removal. */
115    public static final int ACTION_ELEMENT_REMOVE = 157;
116
117    /** Action for new file creation. */
118    public static final int ACTION_NEW = 158;
119
120    /** Action that sub choices should be determined. */
121    public static final int ACTION_SUBCHOICES = 159;
122
123    /** Request context attribute for the page from which the editor was opened. */
124    public static final String ATTRIBUTE_EDITCONTEXT = CmsXmlContentEditor.class.getName() + ".ATTRIBUTE_EDITCONTEXT";
125
126    /** Indicates that the content should be checked before executing the direct edit action. */
127    public static final String EDITOR_ACTION_CHECK = "check";
128
129    /** Indicates that the correction of the XML content structure should be confirmed. */
130    public static final String EDITOR_ACTION_CONFIRMCORRECTION = "confirmcorrect";
131
132    /** Indicates an optional element should be created. */
133    public static final String EDITOR_ACTION_ELEMENT_ADD = "addelement";
134
135    /** Indicates an element should be moved down. */
136    public static final String EDITOR_ACTION_ELEMENT_MOVE_DOWN = "elementdown";
137
138    /** Indicates an element should be moved up. */
139    public static final String EDITOR_ACTION_ELEMENT_MOVE_UP = "elementup";
140
141    /** Indicates an optional element should be removed. */
142    public static final String EDITOR_ACTION_ELEMENT_REMOVE = "removeelement";
143
144    /** Indicates a new file should be created. */
145    public static final String EDITOR_ACTION_NEW = CmsDirectEditButtonSelection.VALUE_NEW;
146
147    /** Indicates that sub choices should be determined. */
148    public static final String EDITOR_ACTION_SUBCHOICES = "subchoices";
149
150    /** Indicates that the contents of the current locale should be copied to other locales. */
151    public static final String EDITOR_COPYLOCALE = "copylocale";
152
153    /** Indicates that the correction of the XML content structure was confirmed by the user. */
154    public static final String EDITOR_CORRECTIONCONFIRMED = "correctconfirmed";
155
156    /** Parameter name for the request parameter "choiceelement". */
157    public static final String PARAM_CHOICEELEMENT = "choiceelement";
158
159    /** Parameter name for the request parameter "choicetype". */
160    public static final String PARAM_CHOICETYPE = "choicetype";
161
162    /** Parameter name for the request parameter "editcontext". */
163    public static final String PARAM_EDITCONTEXT = "editcontext";
164
165    /** Parameter name for the request parameter "elementindex". */
166    public static final String PARAM_ELEMENTINDEX = "elementindex";
167
168    /** Parameter name for the request parameter "elementname". */
169    public static final String PARAM_ELEMENTNAME = "elementname";
170
171    /** Parameter name for the request parameter "newlink". */
172    public static final String PARAM_NEWLINK = "newlink";
173
174    /** Constant for the editor type, must be the same as the editors subfolder name in the VFS. */
175    private static final String EDITOR_TYPE = "xmlcontent";
176
177    /** The log object for this class. */
178    private static final Log LOG = CmsLog.getLog(CmsXmlContentEditor.class);
179
180    /** The content object to edit. */
181    private CmsXmlContent m_content;
182
183    /** The currently active tab during form generation. */
184    private CmsXmlContentTab m_currentTab;
185
186    /** The currently active tab index during form generation. */
187    private int m_currentTabIndex;
188
189    /** The element locale. */
190    private Locale m_elementLocale;
191
192    /** The list of tabs that have an element with an error. */
193    private List<CmsXmlContentTab> m_errorTabs;
194
195    /** File object used to read and write contents. */
196    private CmsFile m_file;
197
198    /** The set of help message IDs that have already been used. */
199    private Set<String> m_helpMessageIds;
200
201    /** The content creation mode. */
202    private String m_mode;
203
204    /** Indicates if an optional element is included in the form. */
205    private boolean m_optionalElementPresent;
206
207    /** Parameter stores the name of the choice element to add. */
208    private String m_paramChoiceElement;
209
210    /** Parameter stores the flag if the element to add is a choice type. */
211    private String m_paramChoiceType;
212
213    /** The page in whose context the content is being edited. */
214    private String m_paramEditContext;
215
216    /** Parameter stores the index of the element to add or remove. */
217    private String m_paramElementIndex;
218
219    /** Parameter stores the name of the element to add or remove. */
220    private String m_paramElementName;
221
222    /** The selected model file for the new resource. */
223    private String m_paramModelFile;
224
225    /** Parameter to indicate if a new XML content resource should be created. */
226    private String m_paramNewLink;
227
228    /** The post-create handler class. */
229    private String m_postCreateHandler;
230
231    /** The error handler for the xml content. */
232    private CmsXmlContentErrorHandler m_validationHandler;
233
234    /** The list of tabs that have an element with a warning. */
235    private List<CmsXmlContentTab> m_warningTabs;
236
237    /** Visitor implementation that stored the widgets for the content.  */
238    private CmsXmlContentWidgetVisitor m_widgetCollector;
239
240    /**
241     * Public constructor.<p>
242     *
243     * @param jsp an initialized JSP action element
244     */
245    public CmsXmlContentEditor(CmsJspActionElement jsp) {
246
247        super(jsp);
248    }
249
250    /**
251     * Performs the change element language action of the editor.<p>
252     */
253    public void actionChangeElementLanguage() {
254
255        // save eventually changed content of the editor
256        Locale oldLocale = CmsLocaleManager.getLocale(getParamOldelementlanguage());
257        Locale newLocale = getElementLocale();
258        try {
259            setEditorValues(oldLocale);
260            if (!m_content.validate(getCms()).hasErrors(oldLocale)) {
261                // no errors found in content
262                if (!m_content.hasLocale(newLocale)) {
263                    // check if we should copy the content from a default locale
264                    boolean addNew = true;
265                    List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource());
266                    if (locales.size() > 1) {
267                        // default locales have been set, try to find a match
268                        try {
269                            m_content.copyLocale(locales, newLocale);
270                            addNew = false;
271                        } catch (CmsXmlException e) {
272                            // no matching default locale was available, we will create a new one later
273                        }
274                    }
275                    if (addNew) {
276                        // create new element if selected language element is not present
277                        try {
278                            m_content.addLocale(getCms(), newLocale);
279                        } catch (CmsXmlException e) {
280                            if (LOG.isErrorEnabled()) {
281                                LOG.error(e.getLocalizedMessage(), e);
282                            }
283                        }
284                    }
285                }
286                //save to temporary file
287                writeContent();
288                // set default action to suppress error messages
289                setAction(ACTION_DEFAULT);
290            } else {
291                // errors found, switch back to old language to show errors
292                setParamElementlanguage(getParamOldelementlanguage());
293                // set stored locale to null to reinitialize it
294                m_elementLocale = null;
295            }
296        } catch (Exception e) {
297            // should usually never happen
298            if (LOG.isInfoEnabled()) {
299                LOG.info(e.getLocalizedMessage(), e);
300            }
301        }
302    }
303
304    /**
305     * Deletes the temporary file and unlocks the edited resource when in direct edit mode.<p>
306     *
307     * @param forceUnlock if true, the resource will be unlocked anyway
308     */
309    @Override
310    public void actionClear(boolean forceUnlock) {
311
312        // delete the temporary file
313        deleteTempFile();
314        boolean directEditMode = Boolean.valueOf(getParamDirectedit()).booleanValue();
315        boolean modified = Boolean.valueOf(getParamModified()).booleanValue();
316        if (directEditMode || forceUnlock || !modified) {
317            // unlock the resource when in direct edit mode, force unlock is true or resource was not modified
318            try {
319                getCms().unlockResource(getParamResource());
320            } catch (CmsException e) {
321                // should usually never happen
322                if (LOG.isInfoEnabled()) {
323                    LOG.info(e.getLocalizedMessage(), e);
324                }
325            }
326        }
327    }
328
329    /**
330     * Performs the copy locale action.<p>
331     *
332     * @throws JspException if something goes wrong
333     */
334    public void actionCopyElementLocale() throws JspException {
335
336        try {
337            setEditorValues(getElementLocale());
338            if (!hasValidationErrors()) {
339                // save content of the editor only to the temporary file
340                writeContent();
341                CmsObject cloneCms = getCloneCms();
342                CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId();
343                cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId));
344                // remove eventual release & expiration date from temporary file to make preview work
345                cloneCms.setDateReleased(getParamTempfile(), CmsResource.DATE_RELEASED_DEFAULT, false);
346                cloneCms.setDateExpired(getParamTempfile(), CmsResource.DATE_EXPIRED_DEFAULT, false);
347            }
348        } catch (CmsException e) {
349            // show error page
350            showErrorPage(this, e);
351        }
352    }
353
354    /**
355     * Performs the delete locale action.<p>
356     *
357     * @throws JspException if something goes wrong
358     */
359    public void actionDeleteElementLocale() throws JspException {
360
361        try {
362            Locale loc = getElementLocale();
363            m_content.removeLocale(loc);
364            //write the modified xml content
365            writeContent();
366            List<Locale> locales = m_content.getLocales();
367            if (locales.size() > 0) {
368                // set first locale as new display locale
369                Locale newLoc = locales.get(0);
370                setParamElementlanguage(newLoc.toString());
371                m_elementLocale = newLoc;
372            } else {
373                if (LOG.isErrorEnabled()) {
374                    LOG.error(Messages.get().getBundle().key(Messages.LOG_GET_LOCALES_1, getParamResource()));
375                }
376            }
377
378        } catch (CmsXmlException e) {
379            // an error occurred while trying to delete the locale, stop action
380            showErrorPage(e);
381        } catch (CmsException e) {
382            // should usually never happen
383            if (LOG.isInfoEnabled()) {
384                LOG.info(e.getLocalizedMessage(), e);
385            }
386        }
387    }
388
389    /**
390     * Performs a configurable action performed by the editor.<p>
391     *
392     * The default action is: save resource, clear temporary files and publish the resource directly.<p>
393     *
394     * @throws IOException if a forward fails
395     * @throws ServletException of a forward fails
396     * @throws JspException if including a JSP fails
397     */
398    public void actionDirectEdit() throws IOException, JspException, ServletException {
399
400        // get the action class from the OpenCms runtime property
401        I_CmsEditorActionHandler actionClass = OpenCms.getWorkplaceManager().getEditorActionHandler();
402        if (actionClass == null) {
403            // error getting the action class, save content and exit the editor
404            actionSave();
405            actionExit();
406        } else {
407            actionClass.editorAction(this, getJsp());
408        }
409    }
410
411    /**
412     * Performs the exit editor action.<p>
413     *
414     * @see org.opencms.workplace.editors.CmsEditor#actionExit()
415     */
416    @Override
417    public void actionExit() throws IOException, JspException, ServletException {
418
419        if (getAction() == ACTION_CANCEL) {
420            // save and exit was canceled
421            return;
422        }
423        // unlock resource if we are in direct edit mode
424        actionClear(false);
425        // close the editor
426        actionClose();
427    }
428
429    /**
430     * Moves an element in the xml content either up or down.<p>
431     *
432     * Depends on the given action value.<p>
433     *
434     * @throws JspException if including the error page fails
435     */
436    public void actionMoveElement() throws JspException {
437
438        // set editor values from request
439        try {
440            setEditorValues(getElementLocale());
441        } catch (CmsXmlException e) {
442            // an error occurred while trying to set the values, stop action
443            showErrorPage(e);
444            return;
445        }
446        // get the necessary parameters to move the element
447        int index = 0;
448        try {
449            index = Integer.parseInt(getParamElementIndex());
450        } catch (Exception e) {
451            // ignore, should not happen
452        }
453
454        // get the value to move
455        I_CmsXmlContentValue value = m_content.getValue(getParamElementName(), getElementLocale(), index);
456
457        if (getAction() == ACTION_ELEMENT_MOVE_DOWN) {
458            // move down the value
459            value.moveDown();
460        } else {
461            // move up the value
462            value.moveUp();
463        }
464        if (getValidationHandler().hasWarnings(getElementLocale())) {
465            // there were warnings for the edited content, reset validation handler to avoid display issues
466            resetErrorHandler();
467        }
468        try {
469            // write the modified content to the temporary file
470            writeContent();
471        } catch (CmsException e) {
472            // an error occurred while trying to save
473            showErrorPage(e);
474        }
475    }
476
477    /**
478     * Creates a new XML content item for editing.<p>
479     *
480     * @throws JspException in case something goes wrong
481     */
482    public void actionNew() throws JspException {
483
484        String newFileName = "";
485        try {
486            newFileName = A_CmsResourceCollector.createResourceForCollector(
487                getCms(),
488                m_paramNewLink,
489                getElementLocale(),
490                getParamResource(),
491                getParamModelFile(),
492                getParamMode(),
493                getParamPostCreateHandler());
494            // wipe out parameters for the editor to ensure proper operation
495            setParamNewLink(null);
496            setParamAction(null);
497            setParamResource(newFileName);
498            setAction(ACTION_DEFAULT);
499
500            // create the temporary file to work with
501            setParamTempfile(createTempFile());
502
503            // set the member variables for the content
504            m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL);
505            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getParamModelFile())) {
506                m_content = CmsXmlContentFactory.unmarshal(
507                    getCms(),
508                    getCms().readFile(newFileName, CmsResourceFilter.ALL));
509            } else {
510                m_content = CmsXmlContentFactory.unmarshal(getCms(), m_file);
511            }
512
513        } catch (CmsException e) {
514            if (LOG.isErrorEnabled()) {
515                LOG.error(Messages.get().getBundle().key(Messages.LOG_CREATE_XML_CONTENT_ITEM_1, m_paramNewLink), e);
516            }
517            throw new JspException(e);
518        } finally {
519            try {
520                // delete the new file
521                getCms().deleteResource(newFileName, CmsResource.DELETE_REMOVE_SIBLINGS);
522            } catch (CmsException e) {
523                // ignore
524            }
525        }
526    }
527
528    /**
529     * Performs the preview XML content action in a new browser window.<p>
530     *
531     * @throws IOException if redirect fails
532     * @throws JspException if inclusion of error page fails
533     */
534    public void actionPreview() throws IOException, JspException {
535
536        try {
537            // save content of the editor only to the temporary file
538            setEditorValues(getElementLocale());
539            writeContent();
540            CmsObject cloneCms = getCloneCms();
541            CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId();
542            cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId));
543            // remove eventual release & expiration date from temporary file to make preview work
544            cloneCms.setDateReleased(getParamTempfile(), CmsResource.DATE_RELEASED_DEFAULT, false);
545            cloneCms.setDateExpired(getParamTempfile(), CmsResource.DATE_EXPIRED_DEFAULT, false);
546
547        } catch (CmsException e) {
548            // show error page
549            showErrorPage(this, e);
550        }
551
552        // get preview uri from content handler
553        String previewUri = m_content.getHandler().getPreview(getCms(), m_content, getParamTempfile());
554
555        // create locale request parameter
556        StringBuffer param = new StringBuffer(8);
557        if (previewUri.indexOf('?') != -1) {
558            param.append("&");
559        } else {
560            param.append("?");
561        }
562        param.append(CmsLocaleManager.PARAMETER_LOCALE);
563        param.append("=");
564        param.append(getParamElementlanguage());
565
566        // redirect to the temporary file with currently active element language or to the specified preview uri
567        sendCmsRedirect(previewUri + param);
568    }
569
570    /**
571     * Performs the save content action.<p>
572     *
573     * @see org.opencms.workplace.editors.CmsEditor#actionSave()
574     */
575    @Override
576    public void actionSave() throws JspException {
577
578        actionSave(getElementLocale());
579        if (getAction() != ACTION_CANCEL) {
580            // save successful, set save action
581            setAction(ACTION_SAVE);
582        }
583    }
584
585    /**
586     * Performs the save content action.<p>
587     *
588     * This is also used when changing the element language.<p>
589     *
590     * @param locale the locale to save the content
591     * @throws JspException if including the error page fails
592     */
593    public void actionSave(Locale locale) throws JspException {
594
595        try {
596            setEditorValues(locale);
597            // check if content has errors
598            if (!hasValidationErrors()) {
599                // no errors found, write content and copy temp file contents
600                writeContent();
601                commitTempFile();
602                // set the modified parameter
603                setParamModified(Boolean.TRUE.toString());
604                // update the offline search indices
605                OpenCms.getSearchManager().updateOfflineIndexes();
606            }
607        } catch (CmsException e) {
608            showErrorPage(e);
609        }
610
611    }
612
613    /**
614     * Adds an optional element to the XML content or removes an optional element from the XML content.<p>
615     *
616     * Depends on the given action value.<p>
617     *
618     * @throws JspException if including the error page fails
619     */
620    public void actionToggleElement() throws JspException {
621
622        // set editor values from request
623        try {
624            setEditorValues(getElementLocale());
625        } catch (CmsXmlException e) {
626            // an error occurred while trying to set the values, stop action
627            showErrorPage(e);
628            return;
629        }
630
631        // get the necessary parameters to add/remove the element
632        int index = 0;
633        try {
634            index = Integer.parseInt(getParamElementIndex());
635        } catch (Exception e) {
636            // ignore, should not happen
637        }
638
639        if (getAction() == ACTION_ELEMENT_REMOVE) {
640            // remove the value , first get the value to remove
641            I_CmsXmlContentValue value = m_content.getValue(getParamElementName(), getElementLocale(), index);
642            m_content.removeValue(getParamElementName(), getElementLocale(), index);
643            // check if the value was a choice option and the last one
644            if (value.isChoiceOption()
645                && (m_content.getSubValues(
646                    CmsXmlUtils.removeLastXpathElement(getParamElementName()),
647                    getElementLocale()).size() == 0)) {
648                // also remove the parent choice type value
649                String xpath = CmsXmlUtils.removeLastXpathElement(getParamElementName());
650                m_content.removeValue(xpath, getElementLocale(), CmsXmlUtils.getXpathIndexInt(xpath) - 1);
651            }
652        } else {
653            // add the new value after the clicked element
654            if (m_content.hasValue(getParamElementName(), getElementLocale())) {
655                // when other values are present, increase index to use right position
656                index += 1;
657            }
658            String elementPath = getParamElementName();
659            if (CmsStringUtil.isNotEmpty(getParamChoiceElement())) {
660                // we have to add a choice element, first check if the element to add itself is part of a choice or not
661                boolean choiceType = Boolean.valueOf(getParamChoiceType()).booleanValue();
662                I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementPath);
663                if (!choiceType || (elemType.isChoiceOption() && elemType.isChoiceType())) {
664                    // this is a choice option or a nested choice type to add, remove last element name from xpath
665                    elementPath = CmsXmlUtils.removeLastXpathElement(elementPath);
666                } else {
667                    // this is a choice type to add, first create type element
668                    m_content.addValue(getCms(), elementPath, getElementLocale(), index);
669                    elementPath = CmsXmlUtils.createXpathElement(elementPath, index + 1);
670                    // all eventual following elements to create have to be at first position
671                    index = 0;
672                }
673                // check if there are nested choice elements to add
674                if (CmsXmlUtils.isDeepXpath(getParamChoiceElement())) {
675                    // create all missing elements except the last one
676                    String pathToChoice = CmsXmlUtils.removeLastXpathElement(getParamChoiceElement());
677                    String newPath = elementPath;
678                    while (CmsStringUtil.isNotEmpty(pathToChoice)) {
679                        String createElement = CmsXmlUtils.getFirstXpathElement(pathToChoice);
680                        newPath = CmsXmlUtils.concatXpath(newPath, createElement);
681                        pathToChoice = CmsXmlUtils.isDeepXpath(pathToChoice)
682                        ? CmsXmlUtils.removeFirstXpathElement(pathToChoice)
683                        : null;
684                        I_CmsXmlContentValue newVal = m_content.addValue(getCms(), newPath, getElementLocale(), index);
685                        newPath = newVal.getPath();
686                        // all eventual following elements to create have to be at first position
687                        index = 0;
688                    }
689                    // create the path to the last choice element
690                    elementPath = CmsXmlUtils.concatXpath(
691                        newPath,
692                        CmsXmlUtils.getLastXpathElement(getParamChoiceElement()));
693                } else {
694                    // create the path to the choice element
695                    elementPath += "/" + getParamChoiceElement();
696                }
697            }
698            // add the value
699            m_content.addValue(getCms(), elementPath, getElementLocale(), index);
700        }
701
702        if (getValidationHandler().hasWarnings(getElementLocale())) {
703            // there were warnings for the edited content, reset validation handler to avoid display issues
704            resetErrorHandler();
705        }
706
707        try {
708            // write the modified content to the temporary file
709            writeContent();
710        } catch (CmsException e) {
711            // an error occurred while trying to save
712            showErrorPage(e);
713        }
714
715    }
716
717    /**
718     * Returns the JSON array with information about the choices of a given element.<p>
719     *
720     * The returned array is only filled if the given element has choice options, otherwise an empty array is returned.<br/>
721     * Note: the first array element is an object containing information if the element itself is a choice type,
722     * the following elements are the choice option items.<p>
723     *
724     * @param elementName the element name to check (complete xpath)
725     * @param choiceType flag indicating if the given element name represents a choice type or not
726     * @param checkChoice flag indicating if the element name should be checked if it is a choice option and choice type
727     *
728     * @return the JSON array with information about the choices of a given element
729     */
730    public JSONArray buildElementChoices(String elementName, boolean choiceType, boolean checkChoice) {
731
732        JSONArray choiceElements = new JSONArray();
733        String choiceName = elementName;
734        I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementName);
735        if (checkChoice && elemType.isChoiceOption() && elemType.isChoiceType()) {
736            // the element itself is a choice option and again a choice type, remove the last element to get correct choices
737            choiceName = CmsXmlUtils.removeLastXpathElement(elementName);
738        }
739        // use xpath to get choice information
740        if (m_content.hasChoiceOptions(choiceName, getElementLocale())) {
741            // we have choice options, first add information about type to create
742            JSONObject info = new JSONObject();
743            try {
744                // put information if element is a choice type
745                info.put("choicetype", choiceType);
746                choiceElements.put(info);
747                // get the available choice options for the choice element
748                List<I_CmsXmlSchemaType> options = m_content.getChoiceOptions(choiceName, getElementLocale());
749                for (Iterator<I_CmsXmlSchemaType> i = options.iterator(); i.hasNext();) {
750                    // add the available element options
751                    I_CmsXmlSchemaType type = i.next();
752                    JSONObject option = new JSONObject();
753                    String key = A_CmsWidget.LABEL_PREFIX
754                        + type.getContentDefinition().getInnerName()
755                        + "."
756                        + type.getName();
757                    // add element name, label and help info
758                    option.put("name", type.getName());
759                    option.put("label", keyDefault(key, type.getName()));
760                    option.put("help", keyDefault(key + A_CmsWidget.HELP_POSTFIX, ""));
761                    // add info if the choice itself is a (sub) choice type
762                    option.put("subchoice", type.isChoiceType());
763                    choiceElements.put(option);
764                }
765            } catch (JSONException e) {
766                // ignore, should not happen
767            }
768        }
769        return choiceElements;
770    }
771
772    /**
773     * Builds the HTML String for the element language selector.<p>
774     *
775     * This method has to use the resource request parameter because the temporary file is
776     * not available in the upper button frame.<p>
777     *
778     * @param attributes optional attributes for the &lt;select&gt; tag
779     * @return the HTML for the element language select box
780     */
781    public String buildSelectElementLanguage(String attributes) {
782
783        return buildSelectElementLanguage(attributes, getParamResource(), getElementLocale());
784    }
785
786    /**
787     * Returns the available sub choices for a nested choice element.<p>
788     *
789     * @return the available sub choices for a nested choice element as JSON array string
790     */
791    public String buildSubChoices() {
792
793        String elementPath = getParamElementName();
794
795        // we have to add a choice element, first check if the element to add itself is part of a choice or not
796        boolean choiceType = Boolean.valueOf(getParamChoiceType()).booleanValue();
797        I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementPath);
798        if (!choiceType || (elemType.isChoiceOption() && elemType.isChoiceType())) {
799            // this is a choice option or a nested choice type to add, remove last element name from xpath
800            elementPath = CmsXmlUtils.removeLastXpathElement(elementPath);
801        }
802        elementPath = CmsXmlUtils.concatXpath(elementPath, getParamChoiceElement());
803        return buildElementChoices(elementPath, choiceType, false).toString();
804    }
805
806    /**
807     * @see org.opencms.widgets.I_CmsWidgetDialog#getButtonStyle()
808     */
809    public int getButtonStyle() {
810
811        return getSettings().getUserSettings().getEditorButtonStyle();
812    }
813
814    /**
815     * @see org.opencms.workplace.editors.CmsEditor#getEditorResourceUri()
816     */
817    @Override
818    public String getEditorResourceUri() {
819
820        return getSkinUri() + "editors/" + EDITOR_TYPE + "/";
821    }
822
823    /**
824     * Returns the current element locale.<p>
825     *
826     * @return the current element locale
827     */
828    public Locale getElementLocale() {
829
830        if (m_elementLocale == null) {
831            if (CmsStringUtil.isNotEmpty(getParamElementlanguage()) && !"null".equals(getParamElementlanguage())) {
832                m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage());
833            } else {
834                initElementLanguage();
835                m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage());
836            }
837        }
838        return m_elementLocale;
839    }
840
841    /**
842     * @see org.opencms.widgets.I_CmsWidgetDialog#getHelpMessageIds()
843     */
844    public Set<String> getHelpMessageIds() {
845
846        if (m_helpMessageIds == null) {
847            m_helpMessageIds = new HashSet<String>();
848        }
849        return m_helpMessageIds;
850    }
851
852    /**
853     * Returns the name of the choice element to add.<p>
854     *
855     * @return the name of the choice element to add
856     */
857    public String getParamChoiceElement() {
858
859        return m_paramChoiceElement;
860    }
861
862    /**
863     * Returns the flag if the element to add is a choice type.<p>
864     *
865     * @return the flag if the element to add is a choice type
866     */
867    public String getParamChoiceType() {
868
869        return m_paramChoiceType;
870    }
871
872    /**
873     * Gets the editor context path (usually either a container page path or null).<p>
874     *
875     * @return the editor context path
876     */
877    public String getParamEditContext() {
878
879        return m_paramEditContext;
880    }
881
882    /**
883     * Returns the index of the element to add or remove.<p>
884     *
885     * @return the index of the element to add or remove
886     */
887    public String getParamElementIndex() {
888
889        return m_paramElementIndex;
890    }
891
892    /**
893     * Returns the name of the element to add or remove.<p>
894     *
895     * @return the name of the element to add or remove
896     */
897    public String getParamElementName() {
898
899        return m_paramElementName;
900    }
901
902    /**
903     * Returns the mode.<p>
904     *
905     * @return the mode
906     */
907    public String getParamMode() {
908
909        return m_mode;
910    }
911
912    /**
913     * Returns the parameter that specifies the model file name.<p>
914     *
915     * @return the parameter that specifies the model file name
916     */
917    public String getParamModelFile() {
918
919        return m_paramModelFile;
920    }
921
922    /**
923     * Returns the "new link" parameter.<p>
924     *
925     * @return the "new link" parameter
926     */
927    public String getParamNewLink() {
928
929        return m_paramNewLink;
930    }
931
932    /**
933     * Returns the postCreateHandler.<p>
934     *
935     * @return the postCreateHandler
936     */
937    public String getParamPostCreateHandler() {
938
939        return m_postCreateHandler;
940    }
941
942    /**
943     * @see org.opencms.widgets.I_CmsWidgetDialog#getUserAgent()
944     */
945    public String getUserAgent() {
946
947        return getJsp().getRequest().getHeader(CmsRequestUtil.HEADER_USER_AGENT);
948    }
949
950    /**
951     * Returns the different xml editor widgets used in the form to display.<p>
952     *
953     * @return the different xml editor widgets used in the form to display
954     */
955    public CmsXmlContentWidgetVisitor getWidgetCollector() {
956
957        if (m_widgetCollector == null) {
958            // create an instance of the widget collector
959            m_widgetCollector = new CmsXmlContentWidgetVisitor(null, getElementLocale());
960            m_content.visitAllValuesWith(m_widgetCollector);
961        }
962        return m_widgetCollector;
963    }
964
965    /**
966     * Generates the HTML form for the XML content editor.<p>
967     *
968     * @return the HTML that generates the form for the XML editor
969     */
970    public String getXmlEditorForm() {
971
972        // set "editor mode" attribute (required for link replacement in the root site)
973        getCms().getRequestContext().setAttribute(CmsRequestContext.ATTRIBUTE_EDITOR, Boolean.TRUE);
974
975        // add customized message bundle eventually specified in XSD of XML content
976        addMessages(m_content.getHandler().getMessages(getLocale()));
977        ((CmsMultiMessages)getMessages()).setFallbackHandler(m_content.getHandler().getMessageKeyHandler());
978        // initialize tab lists for error handling before generating the editor form
979        m_errorTabs = new ArrayList<CmsXmlContentTab>();
980        m_warningTabs = new ArrayList<CmsXmlContentTab>();
981
982        return getXmlEditorForm(m_content.getContentDefinition(), "", true, false).toString();
983    }
984
985    /**
986     * Generates the HTML for the end of the HTML editor form page.<p>
987     *
988     * @return the HTML for the end of the HTML editor form page
989     * @throws JspException if including the error page fails
990     */
991    public String getXmlEditorHtmlEnd() throws JspException {
992
993        StringBuffer result = new StringBuffer(16384);
994        if (m_optionalElementPresent) {
995            // disabled optional element(s) present, reset widgets to show help bubbles on optional form entries
996            resetWidgetCollector();
997        }
998        try {
999            // get all widgets from collector
1000            Iterator<String> i = getWidgetCollector().getWidgets().keySet().iterator();
1001            while (i.hasNext()) {
1002                // get the value of the widget
1003                String key = i.next();
1004                I_CmsXmlContentValue value = getWidgetCollector().getValues().get(key);
1005                I_CmsWidget widget = getWidgetCollector().getWidgets().get(key);
1006                result.append(widget.getDialogHtmlEnd(getCms(), this, (I_CmsWidgetParameter)value));
1007
1008            }
1009
1010            // add empty help text layer
1011            result.append("<div class=\"help\" id=\"helpText\" ");
1012            result.append("onmouseover=\"showHelpText();\" onmouseout=\"hideHelpText();\"></div>\n");
1013
1014            // add empty element button layer
1015            result.append("<div class=\"xmlButtons\" id=\"xmlElementButtons\" ");
1016            result.append(
1017                "onmouseover=\"checkElementButtons(true);\" onmouseout=\"checkElementButtons(false);\"></div>\n");
1018
1019            // return the HTML
1020            return result.toString();
1021        } catch (Exception e) {
1022            showErrorPage(e);
1023            return "";
1024        }
1025    }
1026
1027    /**
1028     * Generates the JavaScript includes for the used widgets in the editor form.<p>
1029     *
1030     * @return the JavaScript includes for the used widgets
1031     * @throws JspException if including the error page fails
1032     */
1033    public String getXmlEditorIncludes() throws JspException {
1034
1035        StringBuffer result = new StringBuffer(1024);
1036
1037        // first include general JQuery JS and UI components
1038        result.append("<script  src=\"");
1039        result.append(CmsWorkplace.getSkinUri()).append("jquery/packed/jquery.js");
1040        result.append("\"></script>\n");
1041        result.append("<script  src=\"");
1042        result.append(CmsWorkplace.getSkinUri()).append("jquery/packed/jquery.ui.js");
1043        result.append("\"></script>\n");
1044
1045        // including dialog-helper.js to be used by ADE gallery widgets
1046        result.append("<script  src=\"");
1047        result.append(CmsWorkplace.getSkinUri()).append("components/widgets/dialog-helper.js");
1048        result.append("\"></script>\n");
1049
1050        // import the JavaScript for JSON helper functions
1051        result.append("<script  src=\"");
1052        result.append(CmsWorkplace.getSkinUri()).append("commons/json2.js");
1053        result.append("\"></script>\n");
1054        result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
1055        result.append(CmsWorkplace.getSkinUri()).append("jquery/css/ui-ocms/jquery.ui.css");
1056        result.append("\">\n");
1057        result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
1058        result.append(CmsWorkplace.getSkinUri()).append("jquery/css/ui-ocms/jquery.ui.ocms.css");
1059        result.append("\">\n");
1060
1061        try {
1062            // iterate over unique widgets from collector
1063            Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator();
1064            while (i.hasNext()) {
1065                I_CmsWidget widget = i.next();
1066                result.append(widget.getDialogIncludes(getCms(), this));
1067                result.append("\n");
1068            }
1069        } catch (Exception e) {
1070            showErrorPage(e);
1071        }
1072        return result.toString();
1073    }
1074
1075    /**
1076     * Generates the JavaScript initialization calls for the used widgets in the editor form.<p>
1077     *
1078     * @return the JavaScript initialization calls for the used widgets
1079     * @throws JspException if including the error page fails
1080     */
1081    public String getXmlEditorInitCalls() throws JspException {
1082
1083        StringBuffer result = new StringBuffer(512);
1084        try {
1085            // iterate over unique widgets from collector
1086            Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator();
1087            while (i.hasNext()) {
1088                I_CmsWidget widget = i.next();
1089                result.append(widget.getDialogInitCall(getCms(), this));
1090            }
1091        } catch (Exception e) {
1092            showErrorPage(e);
1093        }
1094        return result.toString();
1095    }
1096
1097    /**
1098     * Generates the JavaScript initialization methods for the used widgets.<p>
1099     *
1100     * @return the JavaScript initialization methods for the used widgets
1101     *
1102     * @throws JspException if an error occurs during JavaScript generation
1103     */
1104    public String getXmlEditorInitMethods() throws JspException {
1105
1106        StringBuffer result = new StringBuffer(1024);
1107
1108        // first create JS for eventual tabs
1109        StringBuffer tabs = new StringBuffer(512);
1110        if (m_content.getHandler().getTabs().size() > 0) {
1111            // we have some tabs defined, initialize them using JQuery
1112            result.append("var xmlSelectedTab = 0;\n");
1113            result.append("var xmlErrorTabs = new Array();\n");
1114            result.append("var xmlWarningTabs = new Array();\n");
1115            tabs.append("\t$xmltabs = $(\"#xmltabs\").tabs({});\n");
1116            tabs.append("\t$xmltabs.tabs(\"select\", xmlSelectedTab);\n");
1117            tabs.append("\tfor (var i=0; i<xmlErrorTabs.length; i++) {\n");
1118            tabs.append("\t\t$(\"#OcmsTabTab\" + xmlErrorTabs[i]).addClass(\"ui-state-error\");\n");
1119            tabs.append("\t}\n");
1120            tabs.append("\tfor (var i=0; i<xmlWarningTabs.length; i++) {\n");
1121            tabs.append("\t\t$(\"#OcmsTabTab\" + xmlWarningTabs[i]).addClass(\"ui-state-warning\");\n");
1122            tabs.append("\t}\n");
1123        }
1124
1125        // create JS for UI dialog
1126        result.append("var dialogTitleAddChoice = \"");
1127        result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_ADD_HL_0));
1128        result.append("\";\n");
1129        result.append("var dialogTitleAddSubChoice = \"");
1130        result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_SUB_ADD_HL_0));
1131        result.append("\";\n");
1132        result.append("var vfsPathEditorForm = \"");
1133        result.append(getJsp().link(CmsEditor.PATH_EDITORS + "xmlcontent/editor_form.jsp"));
1134        result.append("\";\n");
1135        result.append("if (jQuery) {\n");
1136        result.append("$(document).ready(function(){\n");
1137        result.append(tabs);
1138        result.append("\t$(\"#xmladdelementdialog\").dialog({\n");
1139        result.append("\t\ttitle: \"");
1140        result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_ADD_HL_0));
1141        result.append("\",\n");
1142        result.append("\t\tautoOpen: false,\n");
1143        result.append("\t\tbgiframe: true,\n");
1144        result.append("\t\tminHeight: 150,\n");
1145        result.append("\t\tminWidth: 300,\n");
1146        result.append("\t\twidth: 360,\n");
1147        result.append("\t\tmodal: true,\n");
1148        result.append("\t\tbuttons: {  \"");
1149        result.append(key(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0));
1150        result.append("\": function() { $(this).dialog(\"close\"); } }\n");
1151        result.append("\t});\n");
1152        result.append("});\n");
1153        result.append("}\n");
1154
1155        try {
1156            // iterate over unique widgets from collector
1157            Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator();
1158            while (i.hasNext()) {
1159                I_CmsWidget widget = i.next();
1160                result.append(widget.getDialogInitMethod(getCms(), this));
1161                result.append("\n");
1162            }
1163        } catch (Exception e) {
1164            showErrorPage(e);
1165        }
1166        return result.toString();
1167    }
1168
1169    /**
1170     * Returns true if the edited content contains validation errors, otherwise false.<p>
1171     *
1172     * @return true if the edited content contains validation errors, otherwise false
1173     */
1174    public boolean hasValidationErrors() {
1175
1176        return getValidationHandler().hasErrors();
1177    }
1178
1179    /**
1180     * Returns true if the preview is available for the edited xml content.<p>
1181     *
1182     * This method has to use the resource request parameter and read the file from vfs because the temporary file is
1183     * not available in the upper button frame.<p>
1184     *
1185     * @return true if the preview is enabled, otherwise false
1186     */
1187    public boolean isPreviewEnabled() {
1188
1189        try {
1190            // read the original file because temporary file is not created when opening button frame
1191            CmsFile file = getCms().readFile(getParamResource(), CmsResourceFilter.ALL);
1192            CmsXmlContent content = CmsXmlContentFactory.unmarshal(getCloneCms(), file);
1193            return content.getHandler().getPreview(getCms(), m_content, getParamResource()) != null;
1194        } catch (Exception e) {
1195            // error reading or unmarshalling, no preview available
1196            return false;
1197        }
1198    }
1199
1200    /**
1201     * Sets the editor values for the locale with the parameters from the request.<p>
1202     *
1203     * Called before saving the xml content, redisplaying the input form,
1204     * changing the language and adding or removing elements.<p>
1205     *
1206     * @param locale the locale of the content to save
1207     * @throws CmsXmlException if something goes wrong
1208     */
1209    public void setEditorValues(Locale locale) throws CmsXmlException {
1210
1211        List<String> names = m_content.getNames(locale);
1212        Iterator<String> i = names.iterator();
1213        while (i.hasNext()) {
1214            String path = i.next();
1215            I_CmsXmlContentValue value = m_content.getValue(path, locale);
1216            if (value.isSimpleType()) {
1217                // We don't care about the old editor for the 'inheritable' widget configuration,
1218                // so we're using the old getWidget method here
1219                I_CmsWidget widget = value.getContentDefinition().getContentHandler().getWidget(value);
1220                widget.setEditorValue(
1221                    getCms(),
1222                    getJsp().getRequest().getParameterMap(),
1223                    this,
1224                    (I_CmsWidgetParameter)value);
1225            }
1226        }
1227    }
1228
1229    /**
1230     * Sets the name of the choice element to add.<p>
1231     *
1232     * @param choiceElement the name of the choice element to add
1233     */
1234    public void setParamChoiceElement(String choiceElement) {
1235
1236        m_paramChoiceElement = choiceElement;
1237    }
1238
1239    /**
1240     * Sets the flag if the element to add is a choice type.<p>
1241     *
1242     * @param paramChoiceType the flag if the element to add is a choice type
1243     */
1244    public void setParamChoiceType(String paramChoiceType) {
1245
1246        m_paramChoiceType = paramChoiceType;
1247    }
1248
1249    /**
1250     * Sets the edit context URI.<p>
1251     *
1252     * @param editContext the edit context URI.
1253     */
1254    public void setParamEditContext(String editContext) {
1255
1256        m_paramEditContext = editContext;
1257        CmsObject cms = getCms();
1258        if ((cms != null) && (editContext != null)) {
1259            cms.getRequestContext().setAttribute(ATTRIBUTE_EDITCONTEXT, editContext);
1260        }
1261    }
1262
1263    /**
1264     * Sets the index of the element to add or remove.<p>
1265     *
1266     * @param elementIndex the index of the element to add or remove
1267     */
1268    public void setParamElementIndex(String elementIndex) {
1269
1270        m_paramElementIndex = elementIndex;
1271    }
1272
1273    /**
1274     * Sets the name of the element to add or remove.<p>
1275     *
1276     * @param elementName the name of the element to add or remove
1277     */
1278    public void setParamElementName(String elementName) {
1279
1280        m_paramElementName = elementName;
1281    }
1282
1283    /**
1284     * Sets the content creation mode.<p>
1285     *
1286     * @param mode the content creation mode
1287     */
1288    public void setParamMode(String mode) {
1289
1290        m_mode = mode;
1291    }
1292
1293    /**
1294     * Sets the parameter that specifies the model file name.<p>
1295     *
1296     * @param paramMasterFile the parameter that specifies the model file name
1297     */
1298    public void setParamModelFile(String paramMasterFile) {
1299
1300        m_paramModelFile = paramMasterFile;
1301    }
1302
1303    /**
1304     * Sets the "new link" parameter.<p>
1305     *
1306     * @param paramNewLink the "new link" parameter to set
1307     */
1308    public void setParamNewLink(String paramNewLink) {
1309
1310        m_paramNewLink = CmsEncoder.decode(paramNewLink);
1311    }
1312
1313    /**
1314     * Sets the post-create handler class name.<p>
1315     *
1316     * @param handler the post-create handler class name
1317     */
1318    public void setParamPostCreateHandler(String handler) {
1319
1320        m_postCreateHandler = handler;
1321    }
1322
1323    /**
1324     * Determines if the element language selector is shown dependent on the available Locales.<p>
1325     *
1326     * @return true, if more than one Locale is available, otherwise false
1327     */
1328    public boolean showElementLanguageSelector() {
1329
1330        List<Locale> locales = OpenCms.getLocaleManager().getAvailableLocales(getCms(), getParamResource());
1331        if ((locales == null) || (locales.size() < 2)) {
1332            // for less than two available locales, do not create language selector
1333            return false;
1334        }
1335        return true;
1336    }
1337
1338    /**
1339     * @see org.opencms.workplace.tools.CmsToolDialog#useNewStyle()
1340     */
1341    @Override
1342    public boolean useNewStyle() {
1343
1344        return false;
1345    }
1346
1347    /**
1348     * @see org.opencms.workplace.editors.CmsEditor#commitTempFile()
1349     */
1350    @Override
1351    protected void commitTempFile() throws CmsException {
1352
1353        super.commitTempFile();
1354        m_file = getCloneCms().readFile(getParamResource());
1355        m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file);
1356    }
1357
1358    /**
1359     * Makes sure the requested locale node is present in the content document
1360     * by either copying an existing locale node or creating an empty one.<p>
1361     *
1362     * @param locale the requested locale
1363     *
1364     * @return the locale
1365     */
1366    protected Locale ensureLocale(Locale locale) {
1367
1368        // get the default locale for the resource
1369        List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource());
1370        if (m_content != null) {
1371            if (!m_content.hasLocale(locale)) {
1372                try {
1373
1374                    // to copy anything we need at least one locale
1375                    if ((m_content.getLocales().size() > 0)) {
1376                        // required locale not available, check if an existing default locale should be copied as "template"
1377                        try {
1378                            // a list of possible default locales has been set as property, try to find a match
1379                            m_content.copyLocale(locales, locale);
1380
1381                        } catch (CmsException e) {
1382                            m_content.addLocale(getCms(), locale);
1383                        }
1384
1385                    } else {
1386                        m_content.addLocale(getCms(), locale);
1387                    }
1388
1389                    writeContent();
1390                } catch (CmsException e) {
1391                    LOG.error(e.getMessageContainer(), e);
1392                }
1393            }
1394            if (!m_content.hasLocale(locale)) {
1395                // value may have changed because of the copy operation
1396                locale = m_content.getLocales().get(0);
1397            }
1398        }
1399        return locale;
1400    }
1401
1402    /**
1403     * Initializes the editor content when opening the editor for the first time.<p>
1404     *
1405     * Not necessary for the xmlcontent editor.<p>
1406     */
1407    @Override
1408    protected void initContent() {
1409
1410        // nothing to be done for the xmlcontent editor form
1411    }
1412
1413    /**
1414     * Initializes the element language for the first call of the editor.<p>
1415     */
1416    protected void initElementLanguage() {
1417
1418        // get the default locale for the resource
1419        List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource());
1420        Locale locale = locales.get(0);
1421        locale = ensureLocale(locale);
1422        setParamElementlanguage(locale.toString());
1423    }
1424
1425    /**
1426     * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, javax.servlet.http.HttpServletRequest)
1427     */
1428    @Override
1429    protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
1430
1431        // fill the parameter values in the get/set methods
1432        fillParamValues(request);
1433        // set the dialog type
1434        setParamDialogtype(EDITOR_TYPE);
1435
1436        if (getParamNewLink() != null) {
1437            setParamAction(EDITOR_ACTION_NEW);
1438        } else {
1439            // initialize a content object from the temporary file
1440            if ((getParamTempfile() != null) && !"null".equals(getParamTempfile())) {
1441                try {
1442                    m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL);
1443                    m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file);
1444                } catch (CmsException e) {
1445                    // error during initialization, show error page
1446                    try {
1447                        showErrorPage(this, e);
1448                    } catch (JspException exc) {
1449                        // should usually never happen
1450                        if (LOG.isInfoEnabled()) {
1451                            LOG.info(exc);
1452                        }
1453                    }
1454                }
1455            }
1456        }
1457
1458        // set the action for the JSP switch
1459        if (EDITOR_SAVE.equals(getParamAction())) {
1460            setAction(ACTION_SAVE);
1461        } else if (EDITOR_SAVEEXIT.equals(getParamAction())) {
1462            setAction(ACTION_SAVEEXIT);
1463        } else if (EDITOR_EXIT.equals(getParamAction())) {
1464            setAction(ACTION_EXIT);
1465        } else if (EDITOR_ACTION_SUBCHOICES.equals(getParamAction())) {
1466            setAction(ACTION_SUBCHOICES);
1467        } else if (EDITOR_CLOSEBROWSER.equals(getParamAction())) {
1468            // closed browser window accidentally, unlock resource and delete temporary file
1469            actionClear(true);
1470            return;
1471        } else if (EDITOR_ACTION_CHECK.equals(getParamAction())) {
1472            setAction(ACTION_CHECK);
1473        } else if (EDITOR_SAVEACTION.equals(getParamAction())) {
1474            setAction(ACTION_SAVEACTION);
1475            try {
1476                actionDirectEdit();
1477            } catch (Exception e) {
1478                // should usually never happen
1479                if (LOG.isInfoEnabled()) {
1480                    LOG.info(e.getLocalizedMessage(), e);
1481                }
1482            }
1483            setAction(ACTION_EXIT);
1484        } else if (EDITOR_COPYLOCALE.equals(getParamAction())) {
1485            setAction(ACTION_COPYLOCALE);
1486        } else if (EDITOR_DELETELOCALE.equals(getParamAction())) {
1487            setAction(ACTION_DELETELOCALE);
1488        } else if (EDITOR_SHOW.equals(getParamAction())) {
1489            setAction(ACTION_SHOW);
1490        } else if (EDITOR_SHOW_ERRORMESSAGE.equals(getParamAction())) {
1491            setAction(ACTION_SHOW_ERRORMESSAGE);
1492        } else if (EDITOR_CHANGE_ELEMENT.equals(getParamAction())) {
1493            setAction(ACTION_SHOW);
1494            actionChangeElementLanguage();
1495        } else if (EDITOR_ACTION_ELEMENT_ADD.equals(getParamAction())) {
1496            setAction(ACTION_ELEMENT_ADD);
1497            try {
1498                actionToggleElement();
1499            } catch (JspException e) {
1500                if (LOG.isErrorEnabled()) {
1501                    LOG.error(
1502                        org.opencms.workplace.Messages.get().getBundle().key(
1503                            org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
1504                }
1505            }
1506        } else if (EDITOR_ACTION_ELEMENT_REMOVE.equals(getParamAction())) {
1507            setAction(ACTION_ELEMENT_REMOVE);
1508            try {
1509                actionToggleElement();
1510            } catch (JspException e) {
1511                if (LOG.isErrorEnabled()) {
1512                    LOG.error(
1513                        org.opencms.workplace.Messages.get().getBundle().key(
1514                            org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
1515                }
1516            }
1517        } else if (EDITOR_ACTION_ELEMENT_MOVE_DOWN.equals(getParamAction())) {
1518            setAction(ACTION_ELEMENT_MOVE_DOWN);
1519            try {
1520                actionMoveElement();
1521            } catch (JspException e) {
1522                if (LOG.isErrorEnabled()) {
1523                    LOG.error(
1524                        org.opencms.workplace.Messages.get().getBundle().key(
1525                            org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
1526                }
1527            }
1528        } else if (EDITOR_ACTION_ELEMENT_MOVE_UP.equals(getParamAction())) {
1529            setAction(ACTION_ELEMENT_MOVE_UP);
1530            try {
1531                actionMoveElement();
1532            } catch (JspException e) {
1533                if (LOG.isErrorEnabled()) {
1534                    LOG.error(
1535                        org.opencms.workplace.Messages.get().getBundle().key(
1536                            org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
1537                }
1538            }
1539        } else if (EDITOR_ACTION_NEW.equals(getParamAction())) {
1540            setAction(ACTION_NEW);
1541            return;
1542        } else if (EDITOR_PREVIEW.equals(getParamAction())) {
1543            setAction(ACTION_PREVIEW);
1544        } else if (EDITOR_CORRECTIONCONFIRMED.equals(getParamAction())) {
1545            setAction(ACTION_SHOW);
1546            try {
1547                // correct the XML structure before showing the form
1548                correctXmlStructure();
1549            } catch (CmsException e) {
1550                // error during correction
1551                try {
1552                    showErrorPage(this, e);
1553                } catch (JspException exc) {
1554                    // should usually never happen
1555                    if (LOG.isInfoEnabled()) {
1556                        LOG.info(exc);
1557                    }
1558                }
1559            }
1560        } else {
1561            // initial call of editor
1562            setAction(ACTION_DEFAULT);
1563            try {
1564                // lock resource if autolock is enabled in configuration
1565                if (Boolean.valueOf(getParamDirectedit()).booleanValue()) {
1566                    // set a temporary lock in direct edit mode
1567                    checkLock(getParamResource(), CmsLockType.TEMPORARY);
1568                } else {
1569                    // set common lock
1570                    checkLock(getParamResource());
1571                }
1572                // create the temporary file
1573                setParamTempfile(createTempFile());
1574                // initialize a content object from the created temporary file
1575                m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL);
1576                m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file);
1577                // check the XML content against the given XSD
1578                try {
1579                    m_content.validateXmlStructure(new CmsXmlEntityResolver(getCms()));
1580                } catch (CmsXmlException eXml) {
1581                    // validation failed, check the settings for handling the correction
1582                    if (OpenCms.getWorkplaceManager().isXmlContentAutoCorrect()) {
1583                        // correct the XML structure automatically according to the XSD
1584                        correctXmlStructure();
1585                    } else {
1586                        // show correction confirmation dialog
1587                        setAction(ACTION_CONFIRMCORRECTION);
1588                    }
1589                }
1590
1591            } catch (CmsException e) {
1592                // error during initialization
1593                try {
1594                    showErrorPage(this, e);
1595                } catch (JspException exc) {
1596                    // should usually never happen
1597                    if (LOG.isInfoEnabled()) {
1598                        LOG.info(exc);
1599                    }
1600                }
1601            }
1602            // set the initial element language if not given in request parameters
1603            if (getParamElementlanguage() == null) {
1604                initElementLanguage();
1605            } else {
1606                Locale locale = CmsLocaleManager.getLocale(getParamElementlanguage());
1607                ensureLocale(locale);
1608            }
1609        }
1610    }
1611
1612    /**
1613     * Returns the HTML for the element operation buttons add, move, remove.<p>
1614     *
1615     * @param value the value for which the buttons are generated
1616     * @param addElement if true, the button to add an element is shown
1617     * @param removeElement if true, the button to remove an element is shown
1618     * @return the HTML for the element operation buttons
1619     */
1620    private String buildElementButtons(I_CmsXmlContentValue value, boolean addElement, boolean removeElement) {
1621
1622        StringBuffer jsCall = new StringBuffer(512);
1623        String elementName = CmsXmlUtils.removeXpathIndex(value.getPath());
1624
1625        // indicates if at least one button is active
1626        boolean buttonPresent = false;
1627
1628        int index = value.getIndex();
1629
1630        jsCall.append("showElementButtons('");
1631        jsCall.append(elementName);
1632        jsCall.append("', ");
1633        jsCall.append(index);
1634        jsCall.append(", ");
1635
1636        // build the remove element button if required
1637        if (removeElement) {
1638            jsCall.append(Boolean.TRUE);
1639            buttonPresent = true;
1640        } else {
1641            jsCall.append(Boolean.FALSE);
1642        }
1643        jsCall.append(", ");
1644
1645        // build the move down button (move down in API is move up for content editor)
1646        if (index > 0) {
1647            // build active move down button
1648            jsCall.append(Boolean.TRUE);
1649            buttonPresent = true;
1650        } else {
1651            jsCall.append(Boolean.FALSE);
1652        }
1653        jsCall.append(", ");
1654
1655        // build the move up button (move up in API is move down for content editor)
1656        int indexCount = m_content.getIndexCount(elementName, getElementLocale());
1657        if (index < (indexCount - 1)) {
1658            // build active move up button
1659            jsCall.append(Boolean.TRUE);
1660            buttonPresent = true;
1661        } else {
1662            jsCall.append(Boolean.FALSE);
1663        }
1664        jsCall.append(", ");
1665
1666        // build the add element button if required
1667        if (addElement) {
1668            jsCall.append(Boolean.TRUE);
1669            buttonPresent = true;
1670        } else {
1671            jsCall.append(Boolean.FALSE);
1672        }
1673        jsCall.append(", ");
1674
1675        JSONArray newElements = buildElementChoices(elementName, value.isChoiceType(), true);
1676        jsCall.append("'").append(CmsEncoder.escape(newElements.toString(), CmsEncoder.ENCODING_UTF_8)).append("'");
1677        jsCall.append(");");
1678
1679        String result;
1680        if (buttonPresent) {
1681            // at least one button active, create mouseover button
1682            String btIcon = "xmledit.png";
1683            String btAction = jsCall.toString();
1684            // determine icon to use and if a direct click action is possible
1685            if (addElement && removeElement) {
1686                btIcon = "xmledit_del_add.png";
1687            } else if (addElement) {
1688                btIcon = "xmledit_add.png";
1689                // create button action to add element on button click
1690                StringBuffer action = new StringBuffer(128);
1691                action.append("addElement('");
1692                action.append(elementName);
1693                action.append("', ");
1694                action.append(index);
1695                action.append(", '").append(
1696                    CmsEncoder.escape(newElements.toString(), CmsEncoder.ENCODING_UTF_8)).append("'");
1697                action.append(");");
1698                btAction = action.toString();
1699            } else if (removeElement) {
1700                btIcon = "xmledit_del.png";
1701                // create button action to remove element on button click
1702                StringBuffer action = new StringBuffer(128);
1703                action.append("removeElement('");
1704                action.append(elementName);
1705                action.append("', ");
1706                action.append(index);
1707                action.append(");");
1708                btAction = action.toString();
1709            }
1710            StringBuffer href = new StringBuffer(512);
1711            href.append("javascript:");
1712            href.append(btAction);
1713            href.append("\" onmouseover=\"");
1714            href.append(jsCall);
1715            href.append("checkElementButtons(true);\" onmouseout=\"checkElementButtons(false);\" id=\"btimg.");
1716            href.append(elementName).append(".").append(index);
1717            result = button(href.toString(), null, btIcon, Messages.GUI_EDITOR_XMLCONTENT_ELEMENT_BUTTONS_0, 0);
1718        } else {
1719            // no active button, create a spacer
1720            result = buttonBarSpacer(1);
1721        }
1722
1723        return result;
1724    }
1725
1726    /**
1727     * Corrects the XML structure of the edited content according to the XSD.<p>
1728     *
1729     * @throws CmsException if the correction fails
1730     */
1731    private void correctXmlStructure() throws CmsException {
1732
1733        m_content.setAutoCorrectionEnabled(true);
1734        m_content.correctXmlStructure(getCms());
1735        // write the corrected temporary file
1736        writeContent();
1737    }
1738
1739    /**
1740     * Returns the error handler for error handling of the edited xml content.<p>
1741     *
1742     * @return the error handler
1743     */
1744    private CmsXmlContentErrorHandler getValidationHandler() {
1745
1746        if (m_validationHandler == null) {
1747            // errors were not yet checked, do this now and store result in member
1748            m_validationHandler = m_content.validate(getCms());
1749        }
1750        return m_validationHandler;
1751    }
1752
1753    /**
1754     * Generates the HTML form for the XML content editor.<p>
1755     *
1756     * This is a recursive method because nested schemas are possible,
1757     * do not call this method directly.<p>
1758     *
1759     * @param contentDefinition the content definition to start with
1760     * @param pathPrefix for nested xml content
1761     * @param showHelpBubble if the code for a help bubble should be generated
1762     * @param superTabOpened if the super tab is opened
1763     *
1764     * @return the HTML that generates the form for the XML editor
1765     */
1766    private StringBuffer getXmlEditorForm(
1767        CmsXmlContentDefinition contentDefinition,
1768        String pathPrefix,
1769        boolean showHelpBubble,
1770        boolean superTabOpened) {
1771
1772        StringBuffer result = new StringBuffer(1024);
1773        // only show errors if editor is not opened initially
1774        boolean showErrors = (getAction() != ACTION_NEW)
1775            && (getAction() != ACTION_DEFAULT)
1776            && (getAction() != ACTION_ELEMENT_ADD)
1777            && (getAction() != ACTION_ELEMENT_REMOVE)
1778            && (getAction() != ACTION_ELEMENT_MOVE_DOWN)
1779            && (getAction() != ACTION_ELEMENT_MOVE_UP);
1780
1781        try {
1782            // check if we are in a nested content definition
1783            boolean nested = CmsStringUtil.isNotEmpty(pathPrefix);
1784            boolean useTabs = false;
1785            boolean tabOpened = false;
1786            StringBuffer selectedTabScript = new StringBuffer(64);
1787
1788            boolean collapseLabel = false;
1789            boolean firstElement = true;
1790
1791            // show error header once if there were validation errors
1792            if (!nested && showErrors && (getValidationHandler().hasErrors())) {
1793
1794                result.append("<div class=\"ui-widget\">");
1795                result.append(
1796                    "<div class=\"ui-state-error ui-corner-all\" style=\"padding: 0pt 0.7em;\"><div style=\"padding: 3px 0;\">");
1797                result.append(
1798                    "<span class=\"ui-icon ui-icon-alert\" style=\"float: left; margin-right: 0.3em;\"></span>");
1799                boolean differentLocaleErrors = false;
1800                if ((getValidationHandler().getErrors(getElementLocale()) == null)
1801                    || (getValidationHandler().getErrors().size() > getValidationHandler().getErrors(
1802                        getElementLocale()).size())) {
1803
1804                    differentLocaleErrors = true;
1805                    result.append(
1806                        "<span id=\"xmlerrordialogbutton\" class=\"ui-icon ui-icon-newwin\" style=\"float: left; margin-right: 0.3em;\"></span>");
1807                }
1808                result.append(key(Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0));
1809                result.append("</div>");
1810
1811                // show errors in different locales
1812                if (differentLocaleErrors) {
1813
1814                    result.append("<div id=\"xmlerrordialog\" style=\"display: none;\">");
1815                    // iterate through all found errors
1816                    Map<Locale, Map<String, String>> locErrors = getValidationHandler().getErrors();
1817                    Iterator<Map.Entry<Locale, Map<String, String>>> locErrorsIter = locErrors.entrySet().iterator();
1818                    while (locErrorsIter.hasNext()) {
1819                        Map.Entry<Locale, Map<String, String>> locEntry = locErrorsIter.next();
1820                        Locale locale = locEntry.getKey();
1821
1822                        // skip errors in the actual locale
1823                        if (getElementLocale().equals(locale)) {
1824                            continue;
1825                        }
1826
1827                        result.append("<div style=\"padding: 3px;\"><strong>");
1828                        result.append(
1829                            key(
1830                                Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_LANG_1,
1831                                new Object[] {locale.getLanguage()}));
1832                        result.append("</strong></div>\n");
1833                        result.append("<ul>");
1834
1835                        // iterate through the found errors in a different locale
1836                        Map<String, String> elErrors = locEntry.getValue();
1837                        Iterator<Map.Entry<String, String>> elErrorsIter = elErrors.entrySet().iterator();
1838                        while (elErrorsIter.hasNext()) {
1839                            Map.Entry<String, String> elEntry = elErrorsIter.next();
1840                            String nodeName = elEntry.getKey();
1841                            String errorMsg = elEntry.getValue();
1842                            // output the error message
1843                            result.append("<li>");
1844                            result.append(nodeName);
1845                            result.append(": ");
1846                            result.append(errorMsg);
1847                            result.append("</li>\n");
1848                        }
1849
1850                        result.append("</ul>");
1851                    }
1852
1853                    result.append("</div>\n");
1854                    result.append("<script >\n");
1855                    result.append("$(\"#xmlerrordialog\").dialog({\n");
1856                    result.append("\tautoOpen: true,\n");
1857                    result.append("\tbgiframe: true,\n");
1858                    result.append("\twidth: 500,\n");
1859                    result.append("\tposition: 'center',\n");
1860                    result.append("\tdialogClass: 'ui-state-error',\n");
1861                    result.append("\ttitle: '").append(
1862                        key(Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0)).append("',\n");
1863                    result.append("\tmaxHeight: 600\n");
1864                    result.append("});\n");
1865
1866                    result.append(
1867                        "$(\"#xmlerrordialogbutton\").bind(\"click\", function(e) {$(\"#xmlerrordialog\").dialog(\"open\");});\n");
1868                    result.append("</script>");
1869                }
1870                result.append("</div></div>");
1871            }
1872
1873            if (!nested) {
1874                // check if tabs should be shown
1875                useTabs = contentDefinition.getContentHandler().getTabs().size() > 0;
1876                if (useTabs) {
1877                    // we have some tabs available, generate them on first level
1878                    result.append("<div id=\"xmltabs\" class=\"ui-tabs\">\n<ul>\n");
1879                    for (Iterator<CmsXmlContentTab> i = contentDefinition.getContentHandler().getTabs().iterator(); i.hasNext();) {
1880                        CmsXmlContentTab tab = i.next();
1881                        result.append("\t<li id=\"OcmsTabTab").append(tab.getIdName()).append("\"><a href=\"#OcmsTab");
1882                        result.append(tab.getIdName());
1883                        result.append("\"><span>");
1884                        result.append(
1885                            keyDefault(
1886                                A_CmsWidget.LABEL_PREFIX + contentDefinition.getInnerName() + "." + tab.getTabName(),
1887                                tab.getTabName()));
1888                        result.append("</span></a></li>\n");
1889                    }
1890                    result.append("</ul>\n");
1891                }
1892            }
1893
1894            // if xsd:choice then we just need to get one element of the sequence
1895            Iterator<I_CmsXmlSchemaType> it = contentDefinition.getChoiceMaxOccurs() > 0
1896            ? contentDefinition.getTypeSequence().subList(0, 1).iterator()
1897            : contentDefinition.getTypeSequence().iterator();
1898
1899            // iterate the type sequence
1900            while (it.hasNext()) {
1901                // get the type
1902                I_CmsXmlSchemaType type = it.next();
1903
1904                boolean tabCurrentlyOpened = false;
1905
1906                if (useTabs) {
1907                    // check if a tab is starting with this element
1908                    for (int tabIndex = 0; tabIndex < contentDefinition.getContentHandler().getTabs().size(); tabIndex++) {
1909                        CmsXmlContentTab checkTab = contentDefinition.getContentHandler().getTabs().get(tabIndex);
1910                        if (checkTab.getStartName().equals(type.getName())) {
1911                            // a tab is starting, add block element
1912                            if (tabOpened) {
1913                                // close a previously opened tab
1914                                result.append("</table>\n</div>\n");
1915                            }
1916                            result.append("<div id=\"OcmsTab");
1917                            result.append(checkTab.getIdName());
1918                            result.append("\" class=\"ui-tabs-hide\">\n");
1919                            // set necessary values
1920                            tabOpened = true;
1921                            tabCurrentlyOpened = true;
1922                            collapseLabel = checkTab.isCollapsed();
1923                            m_currentTab = checkTab;
1924                            m_currentTabIndex = tabIndex;
1925                            // leave loop
1926                            break;
1927                        }
1928                    }
1929                }
1930
1931                if (firstElement || tabCurrentlyOpened) {
1932                    // create table before first element or if a tab has been opened before
1933                    result.append("<table class=\"xmlTable");
1934                    if (nested && !superTabOpened) {
1935                        // use other style for nested content definition table if tab was not opened on upper level
1936                        result.append("Nested");
1937                    }
1938                    result.append("\">\n");
1939                    firstElement = false;
1940                }
1941
1942                // get the element sequence of the current type
1943                CmsXmlContentValueSequence elementSequence = m_content.getValueSequence(
1944                    pathPrefix + type.getName(),
1945                    getElementLocale());
1946                int elementCount = elementSequence.getElementCount();
1947
1948                // check if value is optional or multiple
1949                boolean addValue = elementCount < elementSequence.getMaxOccurs();
1950                boolean removeValue = elementCount > elementSequence.getMinOccurs();
1951
1952                // assure that at least one element is present in sequence
1953                boolean disabledElement = false;
1954
1955                if ((contentDefinition.getChoiceMaxOccurs() == 0) && (elementCount < 1)) {
1956                    // current element is disabled, create dummy element if it is a nested type or no choice option
1957                    elementCount = 1;
1958                    elementSequence.addValue(getCms(), 0);
1959                    disabledElement = true;
1960                    m_optionalElementPresent = true;
1961                }
1962
1963                // loop through multiple elements
1964                for (int j = 0; j < elementCount; j++) {
1965                    // get value and corresponding widget
1966                    I_CmsXmlContentValue value = elementSequence.getValue(j);
1967
1968                    String key = value.getPath();
1969
1970                    // check if a tab should be preselected for an added, removed or moved up/down element
1971                    if ((m_currentTab != null) && CmsStringUtil.isNotEmpty(getParamElementName())) {
1972                        // an element was modified, add JS to preselect tab
1973                        if (key.startsWith(getParamElementName()) && (selectedTabScript.length() == 0)) {
1974                            selectedTabScript.append("<script >\n\txmlSelectedTab = ").append(
1975                                m_currentTabIndex).append(";\n</script>\n");
1976                        }
1977                    }
1978
1979                    // show errors and/or warnings
1980                    if (showErrors
1981                        && getValidationHandler().hasErrors(getElementLocale())
1982                        && getValidationHandler().getErrors(getElementLocale()).containsKey(key)) {
1983                        // show error message
1984                        if (collapseLabel) {
1985                            result.append("<tr><td class=\"xmlTdError\"><img src=\"");
1986                            result.append(getEditorResourceUri());
1987                            result.append("error.png\" border=\"0\" alt=\"\" align=\"left\" hspace=\"5\">");
1988                            result.append(resolveMacros(getValidationHandler().getErrors(getElementLocale()).get(key)));
1989                            result.append("</td><td></td></tr>\n");
1990                        } else {
1991                            result.append("<tr><td></td><td><img src=\"");
1992                            result.append(getEditorResourceUri());
1993                            result.append("error.png");
1994                            result.append("\" border=\"0\" alt=\"\"></td><td class=\"xmlTdError\">");
1995                            result.append(resolveMacros(getValidationHandler().getErrors(getElementLocale()).get(key)));
1996                            result.append("</td><td></td></tr>\n");
1997                        }
1998
1999                        // mark tab as error tab if tab is present
2000                        String elemName = CmsXmlUtils.getFirstXpathElement(value.getPath());
2001                        if (((m_currentTab != null) && !m_errorTabs.contains(m_currentTab))
2002                            && (elemName.equals(m_currentTab.getStartName())
2003                                || (!CmsXmlUtils.isDeepXpath(value.getPath()) && value.getName().equals(elemName)))) {
2004                            m_errorTabs.add(m_currentTab);
2005                        }
2006
2007                    }
2008                    // warnings can be additional to errors
2009                    if (showErrors
2010                        && getValidationHandler().hasWarnings(getElementLocale())
2011                        && getValidationHandler().getWarnings(getElementLocale()).containsKey(key)) {
2012                        // show warning message
2013                        if (collapseLabel) {
2014                            result.append("<tr><td class=\"xmlTdError\"><img src=\"");
2015                            result.append(getEditorResourceUri());
2016                            result.append("warning.png\" border=\"0\" alt=\"\" align=\"left\" hspace=\"5\">");
2017                            result.append(
2018                                resolveMacros(getValidationHandler().getWarnings(getElementLocale()).get(key)));
2019                            result.append("</td><td></td></tr>\n");
2020                        } else {
2021                            result.append("<tr><td></td><td><img src=\"");
2022                            result.append(getEditorResourceUri());
2023                            result.append("warning.png");
2024                            result.append("\" border=\"0\" alt=\"\"></td><td class=\"xmlTdWarning\">");
2025                            result.append(
2026                                resolveMacros(getValidationHandler().getWarnings(getElementLocale()).get(key)));
2027                            result.append("</td><td></td></tr>\n");
2028                        }
2029
2030                        // mark tab as warning tab if tab is present
2031                        String elemName = CmsXmlUtils.getFirstXpathElement(value.getPath());
2032                        if (((m_currentTab != null) && !m_warningTabs.contains(m_currentTab))
2033                            && (elemName.equals(m_currentTab.getStartName())
2034                                || (!CmsXmlUtils.isDeepXpath(value.getPath()) && value.getName().equals(elemName)))) {
2035                            m_warningTabs.add(m_currentTab);
2036                        }
2037                    }
2038
2039                    // We don't care about the old editor for the 'inheritable' widget configuration,
2040                    // so we're using the old getWidget method here
2041                    I_CmsWidget widget = value.isSimpleType()
2042                    ? contentDefinition.getContentHandler().getWidget(value)
2043                    : null;
2044
2045                    int index = value.getIndex();
2046                    // create label and help bubble cells
2047                    result.append("<tr>");
2048                    if (!collapseLabel) {
2049                        result.append("<td class=\"xmlLabel");
2050                        if (disabledElement) {
2051                            // element is disabled, mark it with CSS
2052                            result.append("Disabled");
2053                        }
2054                        result.append("\">");
2055                        result.append(
2056                            keyDefault(A_CmsWidget.getLabelKey((I_CmsWidgetParameter)value), value.getName()));
2057                        if (elementCount > 1) {
2058                            result.append(" [").append(index + 1).append("]");
2059                        }
2060                        result.append(": </td>");
2061                        if (showHelpBubble
2062                            && (widget != null)
2063                            && (CmsXmlUtils.getXpathIndexInt(value.getPath()) == 1)) {
2064                            // show help bubble only on first element of each content definition
2065                            result.append(widget.getHelpBubble(getCms(), this, (I_CmsWidgetParameter)value));
2066                        } else {
2067                            // create empty cell for all following elements
2068                            result.append(buttonBarSpacer(16));
2069                        }
2070                    }
2071
2072                    // append individual widget HTML cell if element is enabled
2073                    if (disabledElement) {
2074                        // disabled element, show message for optional element
2075                        result.append("<td class=\"xmlTdDisabled maxwidth\">");
2076                        result.append(key(Messages.GUI_EDITOR_XMLCONTENT_OPTIONALELEMENT_0));
2077                        result.append("</td>");
2078
2079                    } else {
2080                        // element is enabled, show it
2081                        if ((widget != null) && value.isSimpleType()) {
2082                            // this is a simple type, display widget
2083                            result.append(widget.getDialogWidget(getCms(), this, (I_CmsWidgetParameter)value));
2084                        } else {
2085                            // recurse into nested type sequence
2086                            result.append("<td class=\"maxwidth\">");
2087                            boolean showHelp = (j == 0);
2088                            superTabOpened = !nested && tabOpened && collapseLabel;
2089                            result.append(
2090                                getXmlEditorForm(
2091                                    ((CmsXmlNestedContentDefinition)value).getNestedContentDefinition(),
2092                                    value.getPath() + "/",
2093                                    showHelp,
2094                                    superTabOpened));
2095                            result.append("</td>");
2096                        }
2097                    }
2098
2099                    // append element operation (add, remove, move) buttons if required
2100                    result.append(buildElementButtons(value, addValue, removeValue));
2101
2102                    // close row
2103                    result.append("</tr>\n");
2104
2105                    // remove disabled element to avoid eventual side effects, e.g. in widget configurations
2106                    if (disabledElement) {
2107                        elementSequence.removeValue(0);
2108                    }
2109
2110                }
2111            }
2112            // close table
2113            result.append("</table>\n");
2114            if (tabOpened) {
2115                // close last open tab
2116                result.append("</div>\n");
2117            }
2118            if (!nested && useTabs) {
2119                // close block element around tabs
2120                result.append("</div>\n");
2121                // mark eventual warning and error tabs
2122                result.append("<script >\n");
2123                for (Iterator<CmsXmlContentTab> i = m_warningTabs.iterator(); i.hasNext();) {
2124                    CmsXmlContentTab checkTab = i.next();
2125                    if (!m_errorTabs.contains(checkTab)) {
2126                        result.append("\txmlWarningTabs[xmlWarningTabs.length] = \"").append(
2127                            checkTab.getIdName()).append("\";\n");
2128                    }
2129                }
2130                for (Iterator<CmsXmlContentTab> i = m_errorTabs.iterator(); i.hasNext();) {
2131                    CmsXmlContentTab checkTab = i.next();
2132                    result.append("\txmlErrorTabs[xmlErrorTabs.length] = \"").append(checkTab.getIdName()).append(
2133                        "\";\n");
2134                }
2135                result.append("</script>\n");
2136            }
2137            if (selectedTabScript.length() > 0) {
2138                result.append(selectedTabScript);
2139            }
2140        } catch (Throwable t) {
2141            LOG.error(Messages.get().getBundle().key(Messages.ERR_XML_EDITOR_0), t);
2142        }
2143        return result;
2144    }
2145
2146    /**
2147     * Resets the error handler member variable to reinitialize the error messages.<p>
2148     */
2149    private void resetErrorHandler() {
2150
2151        m_validationHandler = null;
2152    }
2153
2154    /**
2155     * Resets the widget collector member variable to reinitialize the widgets.<p>
2156     *
2157     * This is needed to display the help messages of optional elements before building the html end of the form.<p>
2158     */
2159    private void resetWidgetCollector() {
2160
2161        m_widgetCollector = null;
2162    }
2163
2164    /**
2165     * Writes the xml content to the vfs and re-initializes the member variables.<p>
2166     *
2167     * @throws CmsException if writing the file fails
2168     */
2169    private void writeContent() throws CmsException {
2170
2171        String decodedContent = m_content.toString();
2172        try {
2173            m_file.setContents(decodedContent.getBytes(getFileEncoding()));
2174        } catch (UnsupportedEncodingException e) {
2175            throw new CmsException(Messages.get().container(Messages.ERR_INVALID_CONTENT_ENC_1, getParamResource()), e);
2176        }
2177        // the file content might have been modified during the write operation
2178        CmsObject cloneCms = getCloneCms();
2179        CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId();
2180        cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId));
2181        m_file = cloneCms.writeFile(m_file);
2182        m_content = CmsXmlContentFactory.unmarshal(cloneCms, m_file);
2183
2184    }
2185}