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.tools.content;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsPropertyDefinition;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.CmsVfsException;
036import org.opencms.i18n.CmsEncoder;
037import org.opencms.i18n.CmsMessages;
038import org.opencms.jsp.CmsJspActionElement;
039import org.opencms.main.CmsException;
040import org.opencms.main.CmsLog;
041import org.opencms.util.CmsStringUtil;
042import org.opencms.workplace.CmsDialog;
043import org.opencms.workplace.CmsWorkplaceSettings;
044
045import java.util.ArrayList;
046import java.util.List;
047import java.util.regex.Pattern;
048import java.util.regex.PatternSyntaxException;
049
050import javax.servlet.http.HttpServletRequest;
051import javax.servlet.http.HttpServletResponse;
052import javax.servlet.jsp.JspException;
053import javax.servlet.jsp.PageContext;
054
055import org.apache.commons.logging.Log;
056
057/**
058 * Provides methods for the change property values dialog.<p>
059 *
060 * @since 6.0.0
061 */
062public class CmsPropertyChange extends CmsDialog {
063
064    /** Value for the action: show result. */
065    public static final int ACTION_SHOWRESULT = 100;
066
067    /** Request parameter value for the action: show result. */
068    public static final String DIALOG_SHOWRESULT = "showresult";
069
070    /** The dialog type. */
071    public static final String DIALOG_TYPE = "propertychange";
072    /** Request parameter name for the property name. */
073    public static final String PARAM_NEWVALUE = "newvalue";
074    /** Request parameter name for the property name. */
075    public static final String PARAM_OLDVALUE = "oldvalue";
076    /** Request parameter name for the property name. */
077    public static final String PARAM_PROPERTYNAME = "propertyname";
078    /** Request parameter name for the property name. */
079    public static final String PARAM_RECURSIVE = "recursive";
080
081    /** The log object for this class. */
082    private static final Log LOG = CmsLog.getLog(CmsPropertyChange.class);
083
084    private List m_changedResources;
085
086    /** The error message. */
087    private String m_errorMessage;
088
089    private String m_paramNewValue;
090    private String m_paramOldValue;
091    private String m_paramPropertyName;
092    private String m_paramRecursive;
093
094    private boolean m_validationErrors;
095
096    /**
097     * Public constructor with JSP action element.<p>
098     *
099     * @param jsp an initialized JSP action element
100     */
101    public CmsPropertyChange(CmsJspActionElement jsp) {
102
103        super(jsp);
104    }
105
106    /**
107     * Public constructor with JSP variables.<p>
108     *
109     * @param context the JSP page context
110     * @param req the JSP request
111     * @param res the JSP response
112     */
113    public CmsPropertyChange(PageContext context, HttpServletRequest req, HttpServletResponse res) {
114
115        this(new CmsJspActionElement(context, req, res));
116    }
117
118    /**
119     * Builds the html for the property definition select box.<p>
120     *
121     * @param cms the CmsObject
122     * @param selectValue the localized value for the "Please select" option
123     * @param attributes optional attributes for the &lt;select&gt; tag
124     * @param selectedValue the value that is currently selected
125     * @return the html for the property definition select box
126     */
127    public static String buildSelectProperty(
128        CmsObject cms,
129        String selectValue,
130        String attributes,
131        String selectedValue) {
132
133        List propertyDef = new ArrayList();
134        try {
135            // get all property definitions
136            propertyDef = cms.readAllPropertyDefinitions();
137        } catch (CmsException e) {
138            // should usually never happen
139            if (LOG.isInfoEnabled()) {
140                LOG.info(e.getLocalizedMessage(), e);
141            }
142        }
143
144        int propertyCount = propertyDef.size();
145        List options = new ArrayList(propertyCount + 1);
146        List values = new ArrayList(propertyCount + 1);
147        options.add(CmsEncoder.escapeXml(selectValue));
148        values.add("");
149        int selectedIndex = 0;
150        int count = 1;
151
152        for (int i = 0; i < propertyCount; i++) {
153            // loop property definitions and get definition name
154            CmsPropertyDefinition currDef = (CmsPropertyDefinition)propertyDef.get(i);
155            if (currDef.getName().equals(selectedValue)) {
156                selectedIndex = count;
157            }
158            options.add(CmsEncoder.escapeXml(currDef.getName()));
159            values.add(CmsEncoder.escapeXml(currDef.getName()));
160            count += 1;
161        }
162
163        CmsDialog wp = new CmsDialog(null);
164        return wp.buildSelect(attributes, options, values, selectedIndex);
165    }
166
167    /**
168     * Changes the property values on the specified resources.<p>
169     *
170     * @throws JspException if problems including sub-elements occur
171     */
172    public void actionChange() throws JspException {
173
174        // save initialized instance of this class in request attribute for included sub-elements
175        getJsp().getRequest().setAttribute(SESSION_WORKPLACE_CLASS, this);
176        try {
177            boolean recursive = Boolean.valueOf(getParamRecursive()).booleanValue();
178            if (performChangeOperation(recursive)) {
179                // if no exception is caused and "true" is returned change property operation was successful
180                setAction(ACTION_SHOWRESULT);
181            } else {
182                // "false" returned, display "please wait" screen
183                getJsp().include(FILE_DIALOG_SCREEN_WAIT);
184            }
185        } catch (Throwable e) {
186            // error while changing property values, show error dialog
187            includeErrorpage(this, e);
188
189        }
190    }
191
192    /**
193     * Builds the html for the result list of resources where the property was changed.<p>
194     *
195     * @return the html for the result list
196     */
197    public String buildResultList() {
198
199        StringBuffer result = new StringBuffer(16);
200        if ((getChangedResources() != null) && (getChangedResources().size() > 0)) {
201            // at least one resource property value has been changed, show list
202            for (int i = 0; i < getChangedResources().size(); i++) {
203                CmsResource res = (CmsResource)getChangedResources().get(i);
204                String resName = getCms().getSitePath(res);
205                result.append(resName);
206                result.append("<br>\n");
207            }
208        } else {
209            // nothing was changed, show message
210            result.append(Messages.get().getBundle(getLocale()).key(Messages.GUI_INPUT_PROPERTYCHANGE_RESULT_NONE_0));
211        }
212        return result.toString();
213    }
214
215    /**
216     * Builds the html for the property definition select box.<p>
217     *
218     * @param attributes optional attributes for the &lt;select&gt; tag
219     * @return the html for the property definition select box
220     */
221    public String buildSelectProperty(String attributes) {
222
223        return buildSelectProperty(
224            getCms(),
225            Messages.get().getBundle(getLocale()).key(Messages.GUI_PLEASE_SELECT_0),
226            attributes,
227            getParamPropertyName());
228    }
229
230    /**
231     * Returns the error message.<p>
232     *
233     * @return the error message
234     */
235    public String getErrorMessage() {
236
237        if (CmsStringUtil.isEmpty(m_errorMessage)) {
238            return "";
239        }
240
241        return m_errorMessage;
242    }
243
244    /**
245     * Returns the value of the newvalue parameter.<p>
246     *
247     * @return the value of the newvalue parameter
248     */
249    public String getParamNewValue() {
250
251        if (m_paramNewValue != null) {
252            return m_paramNewValue;
253        } else {
254            return CmsProperty.DELETE_VALUE;
255        }
256    }
257
258    /**
259     * Returns the value of the oldvalue parametere.<p>
260     *
261     * @return the value of the oldvalue parameter
262     */
263    public String getParamOldValue() {
264
265        return m_paramOldValue;
266    }
267
268    /**
269     * Returns the value of the propertyname parameter.<p>
270     *
271     * @return the value of the propertyname parameter
272     */
273    public String getParamPropertyName() {
274
275        return m_paramPropertyName;
276    }
277
278    /**
279     * Returns the value of the recursive parameter.<p>
280     *
281     * @return the value of the recursive parameter
282     */
283    public String getParamRecursive() {
284
285        return m_paramRecursive;
286    }
287
288    /**
289     * Returns the height for the result list of changed resources.<p>
290     *
291     * @return the height for the result list of changed resources
292     */
293    public String getResultListHeight() {
294
295        if ((getChangedResources() != null) && (getChangedResources().size() > 0)) {
296            int height = getChangedResources().size() * 14;
297            if (height > 300) {
298                height = 300;
299            }
300            return "" + height;
301        } else {
302            return "14";
303        }
304    }
305
306    /**
307     * Returns if validation errors were found.<p>
308     *
309     * @return true if validation errors were found, otherwise false
310     */
311    public boolean hasValidationErrors() {
312
313        return m_validationErrors;
314    }
315
316    /**
317     * Sets the value of the newvalue parameter.<p>
318     *
319     * @param paramNewValue the value of the newvalue parameter
320     */
321    public void setParamNewValue(String paramNewValue) {
322
323        m_paramNewValue = paramNewValue;
324    }
325
326    /**
327     * Sets the value of the oldvalue parameter.<p>
328     *
329     * @param paramOldValue the value of the oldvalue parameter
330     */
331    public void setParamOldValue(String paramOldValue) {
332
333        m_paramOldValue = paramOldValue;
334    }
335
336    /**
337     * Sets the value of the propertyname parameter.<p>
338     *
339     * @param paramPropertyName the value of the propertyname parameter
340     */
341    public void setParamPropertyName(String paramPropertyName) {
342
343        m_paramPropertyName = paramPropertyName;
344    }
345
346    /**
347     * Sets the value of the recursive parameter.<p>
348     *
349     * @param paramRecursive the value of the recursive parameter
350     */
351    public void setParamRecursive(String paramRecursive) {
352
353        m_paramRecursive = paramRecursive;
354    }
355
356    /**
357     * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, javax.servlet.http.HttpServletRequest)
358     */
359    @Override
360    protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
361
362        // fill the parameter values in the get/set methods
363        fillParamValues(request);
364        // set the dialog type
365        setParamDialogtype(DIALOG_TYPE);
366        // set the action for the JSP switch
367        if (DIALOG_OK.equals(getParamAction())) {
368            if (validateParameters()) {
369                // all parameters are valid, proceed
370                setAction(ACTION_OK);
371            } else {
372                // validation error(s), redisplay form
373                setAction(ACTION_DEFAULT);
374            }
375        } else if (DIALOG_WAIT.equals(getParamAction())) {
376            setAction(ACTION_WAIT);
377        } else if (DIALOG_CANCEL.equals(getParamAction())) {
378            setAction(ACTION_CANCEL);
379        } else {
380            setAction(ACTION_DEFAULT);
381            // build title for change property value dialog
382            setParamTitle(Messages.get().getBundle(getLocale()).key(Messages.GUI_TITLE_PROPERTYCHANGE_0));
383        }
384    }
385
386    /**
387     * Sets the error message.<p>
388     *
389     * @param errorMessage the error message to set
390     */
391    protected void setErrorMessage(String errorMessage) {
392
393        m_errorMessage = errorMessage;
394    }
395
396    /**
397     * Sets the validation error flag.<p>
398     *
399     * @param validationErrors the validation error flag, true if validation errors were found
400     */
401    protected void setValidationErrors(boolean validationErrors) {
402
403        m_validationErrors = validationErrors;
404    }
405
406    /**
407     * Returns the changed resources that were affected by the property change action.<p>
408     *
409     * @return the changed resources that were affected by the property change action
410     */
411    private List getChangedResources() {
412
413        return m_changedResources;
414    }
415
416    /**
417     * Performs the main property change value operation on the resource property.<p>
418     *
419     * @param recursive true, if the property value has to be changed recursively, otherwise false
420     * @return true, if the property values are changed successfully, otherwise false
421     * @throws CmsException if changing is not successful
422     */
423    private boolean performChangeOperation(boolean recursive) throws CmsException {
424
425        // on recursive property changes display "please wait" screen
426        if (recursive && !DIALOG_WAIT.equals(getParamAction())) {
427            // return false, this will trigger the "please wait" screen
428            return false;
429        }
430
431        // lock the selected resource
432        checkLock(getParamResource());
433        // change the property values
434        List changedResources = null;
435        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getParamOldValue())) {
436            changedResources = getCms().changeResourcesInFolderWithProperty(
437                getParamResource(),
438                getParamPropertyName(),
439                getParamOldValue(),
440                getParamNewValue(),
441                recursive);
442        } else {
443            changedResources = setPropertyInFolder(
444                getParamResource(),
445                getParamPropertyName(),
446                getParamNewValue(),
447                recursive);
448        }
449        setChangedResources(changedResources);
450        return true;
451    }
452
453    /**
454     * Sets the given property with the given value to the given resource
455     * (potentially recursiv) if it has not been set before.<p>
456     *
457     * Returns a list with all sub resources that have been modified this way.<p>
458     *
459     * @param resourceRootPath the resource on which property definition values are changed
460     * @param propertyDefinition the name of the propertydefinition to change the value
461     * @param newValue the new value of the propertydefinition
462     * @param recursive if true, change recursively all property values on sub-resources (only for folders)
463     *
464     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
465     *
466     * @throws CmsVfsException for now only when the search for the oldvalue failed.
467     * @throws CmsException if operation was not successful
468     */
469    private List setPropertyInFolder(
470        String resourceRootPath,
471        String propertyDefinition,
472        String newValue,
473        boolean recursive) throws CmsException, CmsVfsException {
474
475        CmsObject cms = getCms();
476
477        // collect the resources to look up
478        List resources = new ArrayList();
479        if (recursive) {
480            resources = cms.readResources(resourceRootPath, CmsResourceFilter.IGNORE_EXPIRATION);
481        } else {
482            resources.add(resourceRootPath);
483        }
484
485        List changedResources = new ArrayList(resources.size());
486        CmsProperty newProperty = new CmsProperty(propertyDefinition, null, null);
487        // create permission set and filter to check each resource
488        for (int i = 0; i < resources.size(); i++) {
489            // loop through found resources and check property values
490            CmsResource res = (CmsResource)resources.get(i);
491            CmsProperty property = cms.readPropertyObject(res, propertyDefinition, false);
492            if (property.isNullProperty()) {
493                // change structure value
494                newProperty.setStructureValue(newValue);
495                newProperty.setName(propertyDefinition);
496                cms.writePropertyObject(cms.getRequestContext().removeSiteRoot(res.getRootPath()), newProperty);
497                changedResources.add(res);
498            } else {
499                // nop
500            }
501        }
502        return changedResources;
503    }
504
505    /**
506     * Sets the changed resources that were affected by the property change action.<p>
507     *
508     * @param changedResources the changed resources that were affected by the property change action
509     */
510    private void setChangedResources(List changedResources) {
511
512        m_changedResources = changedResources;
513    }
514
515    /**
516     * Validates the submitted form parameters.<p>
517     *
518     * If parameters are missing, a localized error message String is created.<p>
519     *
520     * @return true if all parameters are correct, otherwise false
521     *
522     */
523    private boolean validateParameters() {
524
525        boolean allOk = true;
526
527        StringBuffer validationErrors = new StringBuffer(32);
528        CmsMessages messages = Messages.get().getBundle(getLocale());
529
530        // check resource parameter presence
531        if (CmsStringUtil.isEmptyOrWhitespaceOnly(getParamResource()) || !getCms().existsResource(getParamResource())) {
532            allOk = false;
533            validationErrors.append(messages.key(Messages.GUI_PROP_CHANGE_VALIDATE_VFS_RESOURCE_0)).append("<br>");
534        }
535
536        // check selected property name
537        if (CmsStringUtil.isEmptyOrWhitespaceOnly(getParamPropertyName())) {
538            allOk = false;
539            validationErrors.append(messages.key(Messages.GUI_PROP_CHANGE_VALIDATE_SELECT_PROPERTY_0)).append("<br>");
540        }
541
542        // check old property value to look up
543        if (CmsStringUtil.isEmptyOrWhitespaceOnly(getParamOldValue())) {
544            allOk = false;
545            validationErrors.append(messages.key(Messages.GUI_PROP_CHANGE_VALIDATE_OLD_PROP_VALUE_0)).append("<br>");
546        } else {
547            try {
548                // check if there is a place holder in the expression pattern
549                // remove it here, because otherwise this is no valid expression pattern
550                String oldValue = getParamOldValue();
551                if (oldValue.contains(CmsStringUtil.PLACEHOLDER_START)
552                    && oldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
553                    oldValue = oldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
554                    oldValue = oldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
555                }
556                // compile regular expression pattern
557                Pattern.compile(oldValue);
558            } catch (PatternSyntaxException e) {
559                allOk = false;
560                validationErrors.append(messages.key(Messages.GUI_PROP_CHANGE_VALIDATE_OLD_PROP_PATTERN_0)).append(
561                    "<br>");
562            }
563        }
564
565        // check new property value
566        if (CmsStringUtil.isEmptyOrWhitespaceOnly(getParamNewValue())) {
567            // if no new value was given, set it to the delete value
568            setParamNewValue(CmsProperty.DELETE_VALUE);
569        }
570
571        setErrorMessage(validationErrors.toString());
572        setValidationErrors(!allOk);
573        return allOk;
574    }
575}