001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.commons;
029
030import org.opencms.configuration.CmsParameterConfiguration;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsPropertyDefinition;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.CmsUser;
036import org.opencms.i18n.CmsEncoder;
037import org.opencms.jsp.CmsJspActionElement;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.security.CmsRole;
042import org.opencms.util.CmsStringUtil;
043import org.opencms.workplace.CmsWorkplaceSettings;
044import org.opencms.workplace.I_CmsDialogHandler;
045import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
046
047import java.util.Iterator;
048import java.util.List;
049
050import org.apache.commons.logging.Log;
051
052import jakarta.servlet.http.HttpServletRequest;
053import jakarta.servlet.http.HttpServletResponse;
054import jakarta.servlet.jsp.JspException;
055import jakarta.servlet.jsp.PageContext;
056
057/**
058 * Provides methods for the customized property dialog.<p>
059 *
060 * This is a special dialog that is used for the different resource types in the workplace.<p>
061 * For the xmlpage resource type, this class is extended in the editor subpackage.<p>
062 *
063 * The following files use this class:
064 * <ul>
065 * <li>/commons/property_custom.jsp
066 * </ul>
067 * <p>
068 *
069 * @since 6.0.0
070 */
071public class CmsPropertyCustom extends CmsPropertyAdvanced {
072
073    /** Value for the action: edit the properties. */
074    public static final int ACTION_EDIT = 500;
075
076    /** The log object for this class. */
077    private static final Log LOG = CmsLog.getLog(CmsPropertyCustom.class);
078
079    /** Helper object holding the information about the customized properties. */
080    private CmsExplorerTypeSettings m_explorerTypeSettings;
081
082    /** Flag to determine if navigation properties are shown. */
083    private boolean m_showNavigation;
084
085    /**
086     * Public constructor with JSP action element.<p>
087     *
088     * @param jsp an initialized JSP action element
089     */
090    public CmsPropertyCustom(CmsJspActionElement jsp) {
091
092        super(jsp);
093    }
094
095    /**
096     * Public constructor with JSP variables.<p>
097     *
098     * @param context the JSP page context
099     * @param req the JSP request
100     * @param res the JSP response
101     */
102    public CmsPropertyCustom(PageContext context, HttpServletRequest req, HttpServletResponse res) {
103
104        this(new CmsJspActionElement(context, req, res));
105    }
106
107    /**
108     * Performs the edit properties action, will be called by the JSP page.<p>
109     *
110     * @param request the HttpServletRequest
111     * @throws JspException if problems including sub-elements occur
112     */
113    @Override
114    public void actionEdit(HttpServletRequest request) throws JspException {
115
116        // save initialized instance of this class in request attribute for included sub-elements
117        getJsp().getRequest().setAttribute(SESSION_WORKPLACE_CLASS, this);
118        try {
119            // save the changes only if resource is properly locked
120            if (isEditable()) {
121                performEditOperation(request);
122            }
123        } catch (Throwable e) {
124            // Cms error defining property, show error dialog
125            includeErrorpage(this, e);
126        }
127    }
128
129    /**
130     * Creates the HTML String for the edit properties form.<p>
131     *
132     * @return the HTML output String for the edit properties form
133     */
134    @Override
135    public String buildEditForm() {
136
137        StringBuffer result = new StringBuffer(2048);
138
139        // check if the properties are editable
140        boolean editable = isEditable();
141
142        // create the column heads
143        result.append("<table border=\"0\" style=\"width:100%\">\n");
144        result.append("<tr>\n");
145        result.append("\t<td class=\"textbold\" nowrap>");
146        result.append(key(Messages.GUI_PROPERTY_0));
147        result.append("</td>\n");
148        result.append("\t<td class=\"textbold\">");
149        result.append(key(Messages.GUI_PROPERTY_VALUE_0));
150        result.append("</td>\n");
151        // empty column for the checkboxes
152        result.append("\t<td class=\"textbold\" style=\"white-space: nowrap;\">");
153        result.append("&nbsp;");
154        result.append("</td>\n");
155        result.append("</tr>\n");
156        result.append("<tr><td><span style=\"height: 6px;\"></span></td></tr>\n");
157
158        // create the text property input rows from explorer type settings
159        result.append(buildTextInput(editable));
160
161        // show navigation properties if enabled in explorer type settings
162        if (showNavigation()) {
163            result.append(buildNavigationProperties(editable));
164        }
165        result.append("</table>");
166
167        return result.toString();
168    }
169
170    /**
171     * Builds the JavaScript to set the property form values delayed.<p>
172     *
173     * The values of the properties are not inserted directly in the &lt;input&gt; tag,
174     * because there is a display issue when the property values are very long.
175     * This method creates JavaScript to set the property input field values delayed.
176     * On the JSP, the code which is created from this method has to be executed delayed after
177     * the creation of the html form, e.g. in the &lt;body&gt; tag with the attribute
178     * onload="window.setTimeout('doSet()',50);".<p>
179     *
180     * @return the JavaScript to set the property form values delayed
181     */
182    @Override
183    public String buildSetFormValues() {
184
185        StringBuffer result = new StringBuffer(1024);
186        Iterator<String> i = getExplorerTypeSettings().getProperties().iterator();
187        // iterate over the customized properties
188        while (i.hasNext()) {
189            String curProperty = i.next();
190            if (getActiveProperties().containsKey(curProperty)) {
191                CmsProperty property = getActiveProperties().get(curProperty);
192                String propValue = property.getValue();
193                if (propValue != null) {
194                    propValue = propValue.trim();
195                    propValue = CmsStringUtil.escapeJavaScript(propValue);
196                    // create the JS output for a single property
197                    result.append("\tdocument.getElementById(\"");
198                    result.append(PREFIX_VALUE);
199                    result.append(curProperty);
200                    result.append("\").value = \"");
201                    result.append(propValue);
202                    result.append("\";\n");
203                }
204            }
205        }
206        // check if the navigation text property value has to be added
207        if (showNavigation() && getActiveProperties().containsKey(CmsPropertyDefinition.PROPERTY_NAVTEXT)) {
208            CmsProperty property = getActiveProperties().get(CmsPropertyDefinition.PROPERTY_NAVTEXT);
209            String propValue = property.getValue();
210            if (propValue != null) {
211                propValue = propValue.trim();
212                propValue = CmsStringUtil.escapeJavaScript(propValue);
213                // create the JS output for a single property
214                result.append("\tdocument.getElementById(\"");
215                result.append(PREFIX_VALUE);
216                result.append(CmsPropertyDefinition.PROPERTY_NAVTEXT);
217                result.append("\").value = \"");
218                result.append(propValue);
219                result.append("\";\n");
220            }
221        }
222        return result.toString();
223    }
224
225    /**
226     * Builds a button row with an "ok", a "cancel" and an "advanced" button.<p>
227     *
228     * @param okAttributes additional attributes for the "ok" button
229     * @param cancelAttributes additional attributes for the "cancel" button
230     * @param advancedAttributes additional attributes for the "advanced" button
231     * @return the button row
232     */
233    @Override
234    public String dialogButtonsOkCancelAdvanced(
235        String okAttributes,
236        String cancelAttributes,
237        String advancedAttributes) {
238
239        if (isEditable()) {
240            int okButton = BUTTON_OK;
241            if ((getParamDialogmode() != null) && getParamDialogmode().startsWith(MODE_WIZARD)) {
242                // in wizard mode, display finish button instead of ok button
243                okButton = BUTTON_FINISH;
244            }
245            // hide "advanced" button
246            if (isHideButtonAdvanced()) {
247                return dialogButtons(
248                    new int[] {okButton, BUTTON_CANCEL},
249                    new String[] {okAttributes, cancelAttributes});
250            }
251            // show "advanced" button
252            return dialogButtons(
253                new int[] {okButton, BUTTON_CANCEL, BUTTON_ADVANCED},
254                new String[] {okAttributes, cancelAttributes, advancedAttributes});
255        } else {
256            // hide "advanced" button
257            if (isHideButtonAdvanced()) {
258                return dialogButtons(new int[] {BUTTON_CLOSE}, new String[] {cancelAttributes});
259            }
260            // show "advanced" button
261            return dialogButtons(
262                new int[] {BUTTON_CLOSE, BUTTON_ADVANCED},
263                new String[] {cancelAttributes, advancedAttributes});
264        }
265    }
266
267    /**
268     * Returns the explorer type settings for the current resource type.<p>
269     *
270     * @return the explorer type settings for the current resource type
271     */
272    public CmsExplorerTypeSettings getExplorerTypeSettings() {
273
274        return m_explorerTypeSettings;
275    }
276
277    /**
278     * Sets the explorer type settings for the current resource type.<p>
279     *
280     * @param typeSettings the explorer type settings for the current resource type
281     */
282    public void setExplorerTypeSettings(CmsExplorerTypeSettings typeSettings) {
283
284        m_explorerTypeSettings = typeSettings;
285    }
286
287    /**
288     * Sets if navigation properties are shown.<p>
289     *
290     * @param showNav true, if navigation properties are shown, otherwise false
291     */
292    public void setShowNavigation(boolean showNav) {
293
294        m_showNavigation = showNav;
295    }
296
297    /**
298     * Returns if navigation properties are shown.<p>
299     *
300     * @return true, if navigation properties are shown, otherwise false
301     */
302    public boolean showNavigation() {
303
304        return m_showNavigation;
305    }
306
307    /**
308     * Builds the HTML code for the special properties of an xmlpage resource.<p>
309     *
310     * @param editable indicates if the properties are editable
311     * @return the HTML code for the special properties of a file resource
312     */
313    protected StringBuffer buildNavigationProperties(boolean editable) {
314
315        StringBuffer result = new StringBuffer(1024);
316
317        // create "disabled" attribute if properties are not editable
318        String disabled = "";
319        if (!editable) {
320            disabled = " disabled=\"disabled\"";
321        }
322
323        // create "add to navigation" checkbox
324        result.append(buildTableRowStart(key(Messages.GUI_PROPERTY_ADD_TO_NAV_0)));
325        result.append(
326            "<input type=\"checkbox\" name=\"enablenav\" id=\"enablenav\" value=\"true\" onClick=\"toggleNav();\"");
327        if (getActiveProperties().containsKey(CmsPropertyDefinition.PROPERTY_NAVTEXT)
328            || getActiveProperties().containsKey(CmsPropertyDefinition.PROPERTY_NAVPOS)) {
329            result.append(" checked=\"checked\"");
330        }
331        result.append(disabled);
332        result.append(">");
333        result.append("</td>\n");
334        result.append("\t<td class=\"textcenter\">");
335        result.append("&nbsp;");
336        result.append(buildTableRowEnd());
337
338        // create NavText input row
339        result.append(
340            buildPropertyEntry(CmsPropertyDefinition.PROPERTY_NAVTEXT, key(Messages.GUI_LABEL_NAVTEXT_0), editable));
341
342        // create NavPos select box row
343        result.append(buildTableRowStart(key(Messages.GUI_CHNAV_INSERT_AFTER_0)));
344        result.append(
345            CmsChnav.buildNavPosSelector(
346                getCms(),
347                getParamResource(),
348                disabled + " id=\"navpos\" class=\"maxwidth noborder\"",
349                getMessages()));
350        // get the old NavPos value and store it in hidden field
351        String navPos = null;
352        try {
353            navPos = getCms().readPropertyObject(
354                getParamResource(),
355                CmsPropertyDefinition.PROPERTY_NAVPOS,
356                false).getValue();
357        } catch (CmsException e) {
358            // should usually never happen
359            if (LOG.isInfoEnabled()) {
360                LOG.info(e.getLocalizedMessage());
361            }
362        }
363        if (navPos == null) {
364            navPos = "";
365        }
366        result.append("<input type=\"hidden\" name=\"");
367        result.append(PREFIX_HIDDEN);
368        result.append(CmsPropertyDefinition.PROPERTY_NAVPOS);
369        result.append("\" value=\"");
370        result.append(navPos);
371        result.append("\">");
372        result.append("</td>\n");
373        result.append("\t<td class=\"textcenter\">");
374        result.append("&nbsp;");
375        result.append(buildTableRowEnd());
376
377        return result;
378    }
379
380    /**
381     * Builds the html for a single text input property row.<p>
382     *
383     * The html does not include the value of the created property,
384     * the values are set delayed (see buildSetFormValues() for details).<p>
385     *
386     * @param propertyName the name of the property
387     * @param propertyTitle the nice name of the property
388     * @param editable indicates if the properties are editable
389     * @return the html for a single text input property row
390     */
391    protected StringBuffer buildPropertyEntry(String propertyName, String propertyTitle, boolean editable) {
392
393        StringBuffer result = new StringBuffer(256);
394        // create "disabled" attribute if properties are not editable
395        String disabled = "";
396        if (!editable) {
397            disabled = " disabled=\"disabled\"";
398        }
399        result.append(buildTableRowStart(propertyTitle));
400        // the property is used, so create text field with checkbox and hidden field
401        CmsProperty currentProperty = getActiveProperties().get(propertyName);
402        String propValue = "";
403        if (currentProperty != null) {
404            propValue = currentProperty.getValue();
405        }
406        if (propValue != null) {
407            propValue = propValue.trim();
408        }
409        propValue = CmsEncoder.escapeXml(propValue);
410
411        // create text input field
412        result.append("<input type=\"text\" class=\"maxwidth\"");
413        result.append(" name=\"");
414        result.append(PREFIX_VALUE);
415        result.append(propertyName);
416        result.append("\" id=\"");
417        result.append(PREFIX_VALUE);
418        result.append(propertyName);
419        result.append("\"");
420        if (editable) {
421            result.append(" onKeyup=\"checkValue('");
422            result.append(propertyName);
423            result.append("');\"");
424        }
425        result.append(disabled);
426        result.append(">");
427
428        // create hidden field for value
429        result.append("<input type=\"hidden\" name=\"");
430        result.append(PREFIX_HIDDEN);
431        result.append(propertyName);
432        result.append("\" id=\"");
433        result.append(PREFIX_HIDDEN);
434        result.append(propertyName);
435        result.append("\" value=\"");
436        result.append(propValue);
437        result.append("\">");
438        result.append("</td>\n");
439        result.append("\t<td class=\"propertydialog-checkboxcell\">");
440
441        // create activate/deactivate checkbox
442        result.append("<input type=\"checkbox\" name=\"");
443        result.append(PREFIX_USEPROPERTY);
444        result.append(propertyName);
445        result.append("\" id=\"");
446        result.append(PREFIX_USEPROPERTY);
447        result.append(propertyName);
448        result.append("\" value=\"true\"");
449        if (CmsStringUtil.isNotEmpty(propValue)) {
450            result.append(" checked=\"checked\"");
451        }
452        if (editable) {
453            result.append(" onClick=\"toggleDelete('");
454            result.append(propertyName);
455            result.append("');\"");
456        }
457        result.append(disabled + ">");
458
459        result.append(buildTableRowEnd());
460        return result;
461    }
462
463    /**
464     * Builds the HTML for the end of a table row for a single property.<p>
465     *
466     * @return the HTML code for a table row end
467     */
468    protected String buildTableRowEnd() {
469
470        return "</td>\n</tr>\n";
471    }
472
473    /**
474     * Builds the HTML for the start of a table row for a single property.<p>
475     *
476     * @param propertyName the name of the current property
477     * @return the HTML code for the start of a table row
478     */
479    protected StringBuffer buildTableRowStart(String propertyName) {
480
481        StringBuffer result = new StringBuffer(96);
482        result.append("<tr>\n");
483        result.append("\t<td style=\"white-space: nowrap;\" unselectable=\"on\">");
484        result.append(propertyName);
485        result.append("</td>\n");
486        result.append("\t<td class=\"maxwidth\">");
487        return result;
488    }
489
490    /**
491     * Builds the HTML for the common text input property values stored in the String array "PROPERTIES".<p>
492     *
493     * @param editable indicates if the properties are editable
494     * @return the HTML code for the common text input fields
495     */
496    protected StringBuffer buildTextInput(boolean editable) {
497
498        StringBuffer result = new StringBuffer(256);
499        Iterator<String> i = getExplorerTypeSettings().getProperties().iterator();
500        // iterate over the properties
501        while (i.hasNext()) {
502            String curProperty = i.next();
503            result.append(buildPropertyEntry(curProperty, curProperty, editable));
504        }
505        return result;
506    }
507
508    /**
509     * Initializes the explorer type settings for the current resource type.<p>
510     */
511    protected void initExplorerTypeSettings() {
512
513        try {
514            CmsResource res = getCms().readResource(getParamResource(), CmsResourceFilter.ALL);
515            if (res.isFolder()) {
516                if (!getParamResource().endsWith("/")) {
517                    // append folder separator to resource name
518                    setParamResource(getParamResource() + "/");
519                }
520            }
521            String resTypeName = OpenCms.getResourceManager().getResourceType(res.getTypeId()).getTypeName();
522            // get settings for resource type
523            setExplorerTypeSettings(getSettingsForType(resTypeName));
524            setShowNavigation(getExplorerTypeSettings().isShowNavigation());
525        } catch (Throwable e) {
526            // error reading file, show error dialog
527            try {
528                includeErrorpage(this, e);
529            } catch (JspException exc) {
530                LOG.error(
531                    Messages.get().getBundle().key(Messages.LOG_ERROR_INCLUDE_FAILED_1, FILE_DIALOG_SCREEN_ERRORPAGE));
532            }
533        }
534    }
535
536    /**
537     * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, jakarta.servlet.http.HttpServletRequest)
538     */
539    @Override
540    protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
541
542        // fill the parameter values in the get/set methods
543        fillParamValues(request);
544        // get the explorer type settings for the current resource
545        initExplorerTypeSettings();
546        // set the dialog type
547        setParamDialogtype(DIALOG_TYPE);
548        boolean isPopup = Boolean.valueOf(getParamIsPopup()).booleanValue();
549        // set the action for the JSP switch
550        if (DIALOG_SHOW_DEFAULT.equals(getParamAction())) {
551            // save changed properties and redirect to the default OpenCms dialog
552            setAction(ACTION_DEFAULT);
553            try {
554                actionEdit(request);
555                sendForward(CmsPropertyAdvanced.URI_PROPERTY_DIALOG, paramsAsParameterMap());
556                return;
557            } catch (Exception e) {
558                // should usually never happen
559                if (LOG.isInfoEnabled()) {
560                    LOG.info(e.getLocalizedMessage());
561                }
562            }
563        } else if (DIALOG_SAVE_EDIT.equals(getParamAction())) {
564            // save the edited properties
565            if (isPopup) {
566                setAction(ACTION_CLOSEPOPUP_SAVE);
567            } else {
568                setAction(ACTION_SAVE_EDIT);
569            }
570        } else if (DIALOG_CANCEL.equals(getParamAction())) {
571            // save the edited properties
572            if (isPopup) {
573                setAction(ACTION_CLOSEPOPUP);
574            } else {
575                setAction(ACTION_CANCEL);
576            }
577        } else {
578            setAction(ACTION_EDIT);
579            String resName = CmsResource.getName(getParamResource());
580            if (CmsResource.isTemporaryFileName(resName)) {
581                resName = resName.substring(1);
582            }
583            setParamTitle(key(Messages.GUI_PROPERTIES_1, new Object[] {resName}));
584        }
585    }
586
587    /**
588     * Checks the optional parameters of the handler configuration. <p>
589     *
590     * Decides if the "advanced" button should be shown or not.
591     * The "advanced" button is shown if no parameters are given (default)
592     * or the "hideadvanced" attribute is set to false. The "advanced" button
593     * is hidden only, if "hideadvanced" is true and the user is not a member
594     * of the specified user groups.
595     *
596     * @return  false if the "advanced" button is shown (default) <br>
597     *          true if the "advanced" button is hidden
598     */
599    protected boolean isHideButtonAdvanced() {
600
601        I_CmsDialogHandler handler = OpenCms.getWorkplaceManager().getDialogHandler(getDialogHandler());
602        CmsParameterConfiguration handlerParams = handler.getConfiguration();
603        if ((handlerParams != null) && handlerParams.containsKey(PARAM_HIDEADVANCED)) {
604            // checks if "hideadvanced" is set to true
605            boolean isHideAdvancedSet = false;
606            List<String> hAdvanced = handlerParams.getList(PARAM_HIDEADVANCED);
607            if (!hAdvanced.isEmpty()) {
608                isHideAdvancedSet = Boolean.valueOf(hAdvanced.get(0)).booleanValue();
609            }
610            if (isHideAdvancedSet) {
611                // if user has the role root admin
612                if (OpenCms.getRoleManager().hasRole(getCms(), CmsRole.ROOT_ADMIN)) {
613                    return false;
614                }
615                if (handlerParams.containsKey(PARAM_SHOWGROUP)) {
616                    // check if user is one of the configured groups
617                    CmsUser currentUser = getCms().getRequestContext().getCurrentUser();
618                    List<String> confGroups = handlerParams.getList(PARAM_SHOWGROUP);
619                    for (String groupName : confGroups) {
620                        try {
621                            if (getCms().userInGroup(currentUser.getName(), groupName)) {
622                                return false;
623                            }
624                        } catch (CmsException e) {
625                            LOG.error(e.getLocalizedMessage(), e);
626                        }
627                    }
628                }
629                return true;
630            }
631        }
632        return false;
633    }
634
635    /**
636     * Performs the editing of the resources properties.<p>
637     *
638     * @param request the HttpServletRequest
639     * @return true, if the properties were successfully changed, otherwise false
640     * @throws CmsException if editing is not successful
641     */
642    protected boolean performEditOperation(HttpServletRequest request) throws CmsException {
643
644        boolean useTempfileProject = Boolean.valueOf(getParamUsetempfileproject()).booleanValue();
645        try {
646            if (useTempfileProject) {
647                switchToTempProject();
648            }
649            // write the common properties defined in the explorer type settings
650            Iterator<String> i = getExplorerTypeSettings().getProperties().iterator();
651            // iterate over the properties
652            while (i.hasNext()) {
653                String curProperty = i.next();
654                String paramValue = request.getParameter(PREFIX_VALUE + curProperty);
655                String oldValue = request.getParameter(PREFIX_HIDDEN + curProperty);
656                writeProperty(curProperty, paramValue, oldValue);
657            }
658
659            // write the navigation properties if enabled
660            if (showNavigation()) {
661                // get the navigation enabled parameter
662                String paramValue = request.getParameter("enablenav");
663                String oldValue = null;
664                if (Boolean.valueOf(paramValue).booleanValue()) {
665                    // navigation enabled, update params
666                    paramValue = request.getParameter("navpos");
667                    if (!"-1".equals(paramValue) && !String.valueOf(Float.MAX_VALUE).equals(paramValue)) {
668                        // update the property only when it is different from "-1" (meaning no change)
669                        oldValue = request.getParameter(PREFIX_HIDDEN + CmsPropertyDefinition.PROPERTY_NAVPOS);
670                        writeProperty(CmsPropertyDefinition.PROPERTY_NAVPOS, paramValue, oldValue);
671                    }
672                    paramValue = request.getParameter(PREFIX_VALUE + CmsPropertyDefinition.PROPERTY_NAVTEXT);
673                    oldValue = request.getParameter(PREFIX_HIDDEN + CmsPropertyDefinition.PROPERTY_NAVTEXT);
674                    writeProperty(CmsPropertyDefinition.PROPERTY_NAVTEXT, paramValue, oldValue);
675                } else {
676                    // navigation disabled, delete property values
677                    writeProperty(CmsPropertyDefinition.PROPERTY_NAVPOS, null, null);
678                    writeProperty(CmsPropertyDefinition.PROPERTY_NAVTEXT, null, null);
679                }
680            }
681        } finally {
682            if (useTempfileProject) {
683                switchToCurrentProject();
684            }
685        }
686        return true;
687    }
688
689    /**
690     * Writes a property value for a resource, if the value was changed.<p>
691     *
692     * If a property definition for the resource does not exist,
693     * it is automatically created by this method.<p>
694     *
695     * @param propName the name of the property
696     * @param propValue the new value of the property
697     * @param oldValue the old value of the property
698     * @throws CmsException if something goes wrong
699     */
700    protected void writeProperty(String propName, String propValue, String oldValue) throws CmsException {
701
702        // get the current property object
703        CmsProperty currentProperty = getActiveProperties().get(propName);
704        if (currentProperty == null) {
705            // new property, create new property object
706            currentProperty = new CmsProperty();
707            currentProperty.setName(propName);
708        } else {
709            // clone the property, because the original property is frozen
710            currentProperty = currentProperty.cloneAsProperty();
711        }
712
713        // check if there is a parameter value for the current property
714        boolean emptyParam = true;
715        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(propValue)) {
716            emptyParam = false;
717        }
718
719        if (emptyParam) {
720            // parameter is empty, check if the property has to be deleted
721            if (getActiveProperties().containsKey(propName)) {
722                // lock resource if autolock is enabled
723                checkLock(getParamResource());
724                // determine the value to delete
725                if (currentProperty.getStructureValue() != null) {
726                    currentProperty.setStructureValue(CmsProperty.DELETE_VALUE);
727                    currentProperty.setResourceValue(null);
728                } else {
729                    currentProperty.setResourceValue(CmsProperty.DELETE_VALUE);
730                    currentProperty.setStructureValue(null);
731                }
732                // write the updated property object
733                getCms().writePropertyObject(getParamResource(), currentProperty);
734            }
735        } else {
736            // parameter is not empty, check if the value has changed
737            if (!propValue.equals(oldValue)) {
738                // lock resource if autolock is enabled
739                checkLock(getParamResource());
740                if ((currentProperty.getStructureValue() == null) && (currentProperty.getResourceValue() == null)) {
741                    // new property, determine setting from OpenCms workplace configuration
742                    if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) {
743                        currentProperty.setStructureValue(propValue);
744                        currentProperty.setResourceValue(null);
745                    } else {
746                        currentProperty.setResourceValue(propValue);
747                        currentProperty.setStructureValue(null);
748                    }
749                } else if (currentProperty.getStructureValue() != null) {
750                    // structure value has to be updated
751                    currentProperty.setStructureValue(propValue);
752                    currentProperty.setResourceValue(null);
753                } else {
754                    // resource value has to be updated
755                    currentProperty.setResourceValue(propValue);
756                    currentProperty.setStructureValue(null);
757                }
758                // set auto-creation of the property to true
759                currentProperty.setAutoCreatePropertyDefinition(true);
760                // write the updated property object
761                getCms().writePropertyObject(getParamResource(), currentProperty);
762            }
763        }
764    }
765}