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.file.CmsResource;
031import org.opencms.file.CmsResource.CmsResourceCopyMode;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.file.CmsVfsException;
034import org.opencms.file.CmsVfsResourceAlreadyExistsException;
035import org.opencms.file.CmsVfsResourceNotFoundException;
036import org.opencms.jsp.CmsJspActionElement;
037import org.opencms.lock.CmsLockException;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.security.CmsPermissionSet;
042import org.opencms.staticexport.CmsLinkManager;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.workplace.CmsMultiDialog;
045import org.opencms.workplace.CmsWorkplaceSettings;
046
047import java.util.ArrayList;
048import java.util.Iterator;
049import java.util.List;
050
051import org.apache.commons.logging.Log;
052
053import jakarta.servlet.http.HttpServletRequest;
054import jakarta.servlet.http.HttpServletResponse;
055import jakarta.servlet.jsp.JspException;
056import jakarta.servlet.jsp.PageContext;
057
058/**
059 * Provides methods for the copy resources dialog.<p>
060 *
061 * The following files use this class:
062 * <ul>
063 * <li>/commons/copy.jsp
064 * </ul>
065 * <p>
066 *
067 * @since 6.0.0
068 */
069public class CmsCopy extends CmsMultiDialog {
070
071    /** Value for the action: copy the resource. */
072    public static final int ACTION_COPY = 100;
073
074    /** The dialog type. */
075    public static final String DIALOG_TYPE = "copy";
076
077    /** Request parameter name for the keep rights flag. */
078    public static final String PARAM_KEEPRIGHTS = "keeprights";
079
080    /** Request parameter name for the overwrite flag. */
081    public static final String PARAM_OVERWRITE = "overwrite";
082
083    /** The log object for this class. */
084    private static final Log LOG = CmsLog.getLog(CmsCopy.class);
085
086    /** A parameter of this dialog. */
087    private String m_paramCopymode;
088
089    /** A parameter of this dialog. */
090    private String m_paramKeeprights;
091
092    /** A parameter of this dialog. */
093    private String m_paramOverwrite;
094
095    /** A parameter of this dialog. */
096    private String m_paramTarget;
097
098    /**
099     * Public constructor with JSP action element.<p>
100     *
101     * @param jsp an initialized JSP action element
102     */
103    public CmsCopy(CmsJspActionElement jsp) {
104
105        super(jsp);
106    }
107
108    /**
109     * Public constructor with JSP variables.<p>
110     *
111     * @param context the JSP page context
112     * @param req the JSP request
113     * @param res the JSP response
114     */
115    public CmsCopy(PageContext context, HttpServletRequest req, HttpServletResponse res) {
116
117        this(new CmsJspActionElement(context, req, res));
118    }
119
120    /**
121     * Performs the copy action, will be called by the JSP page.<p>
122     *
123     * @throws JspException if problems including sub-elements occur
124     */
125    public void actionCopy() throws JspException {
126
127        // save initialized instance of this class in request attribute for included sub-elements
128        getJsp().getRequest().setAttribute(SESSION_WORKPLACE_CLASS, this);
129        CmsResource resource = null;
130        try {
131            boolean isFolder = false;
132            String source = getResourceList().get(0);
133            String target = CmsLinkManager.getAbsoluteUri(getParamTarget(), CmsResource.getParentFolder(source));
134            if (!isMultiOperation()) {
135                resource = getCms().readResource(source, CmsResourceFilter.ALL);
136                isFolder = resource.isFolder();
137            } else {
138                String siteRootFolder = null;
139                try {
140                    // check if a site root was added to the target name
141                    if (OpenCms.getSiteManager().getSiteRoot(target) != null) {
142                        siteRootFolder = getCms().getRequestContext().getSiteRoot();
143                        if (siteRootFolder.endsWith("/")) {
144                            siteRootFolder = siteRootFolder.substring(0, siteRootFolder.length() - 1);
145                        }
146                        getCms().getRequestContext().setSiteRoot("/");
147                    }
148                    resource = getCms().readResource(target, CmsResourceFilter.ALL);
149                } finally {
150                    if (siteRootFolder != null) {
151                        getCms().getRequestContext().setSiteRoot(siteRootFolder);
152                    }
153                }
154                if (!resource.isFolder()) {
155                    // no folder selected for multi operation, throw exception
156                    throw new CmsVfsException(
157                        Messages.get().container(Messages.ERR_COPY_MULTI_TARGET_NOFOLDER_1, target));
158                }
159            }
160            if (performDialogOperation()) {
161                // if no exception is caused and "true" is returned copy operation was successful
162                if (isMultiOperation() || isFolder) {
163                    // set request attribute to reload the explorer tree view
164                    List<String> folderList = new ArrayList<String>(1);
165                    String targetParent = CmsResource.getParentFolder(target);
166                    folderList.add(targetParent);
167                    getJsp().getRequest().setAttribute(REQUEST_ATTRIBUTE_RELOADTREE, folderList);
168                }
169                actionCloseDialog();
170            } else {
171                // "false" returned, display "please wait" screen
172                getJsp().include(FILE_DIALOG_SCREEN_WAIT);
173            }
174        } catch (Throwable e) {
175            // check if this exception requires a confirmation or error screen for single resource operations
176            if (!isMultiOperation()
177                && ((e instanceof CmsVfsResourceAlreadyExistsException) || (e instanceof CmsLockException))
178                && (resource != null)
179                && !(resource.isFolder())) {
180                // file copy but file already exists, now check target file type
181                int targetType = -1;
182                String storedSiteRoot = null;
183                try {
184                    if (OpenCms.getSiteManager().getSiteRoot(getParamTarget()) != null) {
185                        storedSiteRoot = getCms().getRequestContext().getSiteRoot();
186                        getCms().getRequestContext().setSiteRoot("/");
187                    }
188                    CmsResource targetRes = getCms().readResource(getParamTarget());
189                    targetType = targetRes.getTypeId();
190                } catch (CmsException e2) {
191                    // can usually be ignored
192                    if (LOG.isInfoEnabled()) {
193                        LOG.info(e2.getLocalizedMessage());
194                    }
195                } finally {
196                    if (storedSiteRoot != null) {
197                        getCms().getRequestContext().setSiteRoot(storedSiteRoot);
198                    }
199                }
200                if (resource.getTypeId() == targetType) {
201                    // file type of target is the same as source, show confirmation dialog
202                    setParamMessage(
203                        CmsStringUtil.escapeHtml(
204                            key(
205                                Messages.GUI_COPY_CONFIRM_OVERWRITE_2,
206                                new Object[] {getParamResource(), getParamTarget()})));
207                    getJsp().include(FILE_DIALOG_SCREEN_CONFIRM);
208                } else {
209                    // file type is different, create error message
210                    includeErrorpage(this, e);
211                }
212            } else {
213                // error during copy, show error dialog
214                includeErrorpage(this, e);
215            }
216        }
217    }
218
219    /**
220     * Builds the input radio buttons to select between preserving links or creating new resources when copying.<p>
221     *
222     * @return the HTML code for the radio buttons
223     */
224    public String buildRadioCopyMode() {
225
226        StringBuffer retValue = new StringBuffer(256);
227
228        // check if the current resource is a folder for single operation
229        boolean isFolder = isOperationOnFolder();
230        String checkedAttr = " checked=\"checked\"";
231
232        if (isMultiOperation() || isFolder) {
233            // for multi resource operations or folders, show an additional option "preserve links"
234            CmsResourceCopyMode defaultMode = getSettings().getUserSettings().getDialogCopyFolderMode();
235            retValue.append("<input type=\"radio\" name=\"copymode\" value=\"");
236            retValue.append(CmsResource.COPY_AS_SIBLING.getMode());
237            retValue.append("\"");
238            if (defaultMode == CmsResource.COPY_AS_SIBLING) {
239                retValue.append(checkedAttr);
240            }
241            retValue.append("> ");
242            String msgKey;
243            if (isMultiOperation()) {
244                msgKey = Messages.GUI_COPY_MULTI_CREATE_SIBLINGS_0;
245            } else {
246                msgKey = Messages.GUI_COPY_CREATE_SIBLINGS_0;
247            }
248            retValue.append(key(msgKey));
249            retValue.append("<br>\n");
250            retValue.append("<input type=\"radio\" name=\"copymode\" value=\"");
251            retValue.append(CmsResource.COPY_PRESERVE_SIBLING.getMode());
252            retValue.append("\"");
253            if (defaultMode == CmsResource.COPY_PRESERVE_SIBLING) {
254                retValue.append(checkedAttr);
255            }
256            retValue.append("> ");
257            retValue.append(key(Messages.GUI_COPY_ALL_NO_SIBLINGS_0));
258            retValue.append("<br>\n");
259            retValue.append("<input type=\"radio\" name=\"copymode\" value=\"");
260            retValue.append(CmsResource.COPY_AS_NEW.getMode());
261            retValue.append("\"");
262            if (defaultMode == CmsResource.COPY_AS_NEW) {
263                retValue.append(checkedAttr);
264            }
265            retValue.append("> ");
266            retValue.append(key(Messages.GUI_COPY_ALL_0));
267            retValue.append("<br>\n");
268
269            if (isMultiOperation()) {
270                // show overwrite option for multi resource copy
271                retValue.append(dialogSpacer());
272                retValue.append("<input type=\"checkbox\" name=\"");
273                retValue.append(PARAM_OVERWRITE);
274                retValue.append("\" value=\"true\"> ");
275                retValue.append(key(Messages.GUI_COPY_MULTI_OVERWRITE_0));
276                retValue.append("<br>\n");
277            }
278        } else {
279            // for files, show copy option "copy as sibling" and "copy as new resource"
280            CmsResourceCopyMode defaultMode = getSettings().getUserSettings().getDialogCopyFileMode();
281            retValue.append("<input type=\"radio\" name=\"copymode\" value=\"");
282            retValue.append(CmsResource.COPY_AS_SIBLING.getMode());
283            retValue.append("\"");
284            if (defaultMode == CmsResource.COPY_AS_SIBLING) {
285                retValue.append(checkedAttr);
286            }
287            retValue.append("> ");
288            retValue.append(key(Messages.GUI_CREATE_SIBLING_0));
289            retValue.append("<br>\n");
290            retValue.append("<input type=\"radio\" name=\"copymode\" value=\"");
291            retValue.append(CmsResource.COPY_AS_NEW.getMode());
292            retValue.append("\"");
293            if (defaultMode == CmsResource.COPY_AS_NEW) {
294                retValue.append(checkedAttr);
295            }
296            retValue.append("> ");
297            retValue.append(key(Messages.GUI_COPY_AS_NEW_0));
298            retValue.append("<br>\n");
299        }
300
301        return retValue.toString();
302    }
303
304    /**
305     * Returns the value of the copymode parameter.<p>
306     *
307     * @return the value of the copymode parameter
308     */
309    public String getParamCopymode() {
310
311        return m_paramCopymode;
312    }
313
314    /**
315     * Returns the value of the keeprights parameter.<p>
316     *
317     * @return the value of the keeprights parameter
318     */
319    public String getParamKeeprights() {
320
321        return m_paramKeeprights;
322    }
323
324    /**
325     * Returns the value of the overwrite parameter.<p>
326     *
327     * @return the value of the overwrite parameter
328     */
329    public String getParamOverwrite() {
330
331        return m_paramOverwrite;
332    }
333
334    /**
335     * Returns the value of the target parameter,
336     * or null if this parameter was not provided.<p>
337     *
338     * The target parameter selects the target name
339     * of the operation.<p>
340     *
341     * @return the value of the target parameter
342     */
343    public String getParamTarget() {
344
345        return m_paramTarget;
346    }
347
348    /**
349     * Sets the value of the copymode parameter.<p>
350     *
351     * @param value the value of the copymode parameter
352     */
353    public void setParamCopymode(String value) {
354
355        m_paramCopymode = value;
356    }
357
358    /**
359     * Sets the value of the "keeprights" parameter.<p>
360     *
361     * @param value the value of the "keeprights" parameter
362     */
363    public void setParamKeeprights(String value) {
364
365        m_paramKeeprights = value;
366    }
367
368    /**
369     * Sets the value of the overwrite parameter.<p>
370     *
371     * @param paramOverwrite the value of the overwrite parameter
372     */
373    public void setParamOverwrite(String paramOverwrite) {
374
375        m_paramOverwrite = paramOverwrite;
376    }
377
378    /**
379     * Sets the value of the target parameter.<p>
380     *
381     * @param value the value to set
382     */
383    public void setParamTarget(String value) {
384
385        m_paramTarget = value;
386    }
387
388    /**
389     * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, jakarta.servlet.http.HttpServletRequest)
390     */
391    @Override
392    protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
393
394        // fill the parameter values in the get/set methods
395        fillParamValues(request);
396
397        // check the required permissions to copy the resource
398        if (!checkResourcePermissions(CmsPermissionSet.ACCESS_WRITE, false)) {
399            // no write permissions for the resource, set cancel action to close dialog
400            setParamAction(DIALOG_CANCEL);
401        }
402
403        // set the dialog type
404        setParamDialogtype(DIALOG_TYPE);
405        // set the action for the JSP switch
406        if (DIALOG_TYPE.equals(getParamAction())) {
407            setAction(ACTION_COPY);
408        } else if (DIALOG_CONFIRMED.equals(getParamAction())) {
409            setAction(ACTION_CONFIRMED);
410        } else if (DIALOG_WAIT.equals(getParamAction())) {
411            setAction(ACTION_WAIT);
412        } else if (DIALOG_CANCEL.equals(getParamAction())) {
413            setAction(ACTION_CANCEL);
414        } else {
415            setAction(ACTION_DEFAULT);
416            // build title for copy dialog
417            setDialogTitle(Messages.GUI_COPY_RESOURCE_1, Messages.GUI_COPY_MULTI_2);
418        }
419    }
420
421    /**
422     * Performs the resource copying.<p>
423     *
424     * @return true, if the resource was copied, otherwise false
425     * @throws CmsException if copying is not successful
426     */
427    @Override
428    protected boolean performDialogOperation() throws CmsException {
429
430        // check if the current resource is a folder for single operation
431        boolean isFolder = isOperationOnFolder();
432
433        // on folder copy display "please wait" screen, not for simple file copy
434        if ((isMultiOperation() || isFolder) && !DIALOG_WAIT.equals(getParamAction())) {
435            // return false, this will trigger the "please wait" screen
436            return false;
437        }
438
439        // get the copy mode from request parameter value
440        CmsResourceCopyMode copyMode = CmsResource.COPY_PRESERVE_SIBLING;
441        try {
442            copyMode = CmsResourceCopyMode.valueOf(Integer.parseInt(getParamCopymode()));
443        } catch (Exception e) {
444            // can usually be ignored
445            if (LOG.isInfoEnabled()) {
446                LOG.info(e.getLocalizedMessage());
447            }
448        }
449
450        // check the overwrite options
451        boolean overwrite = Boolean.valueOf(getParamOverwrite()).booleanValue();
452        overwrite = ((isMultiOperation() && overwrite) || DIALOG_CONFIRMED.equals(getParamAction()));
453
454        // calculate the target name
455        String target = getParamTarget();
456        if (target == null) {
457            target = "";
458        }
459
460        String storedSiteRoot = null;
461        try {
462            // check if a site root was added to the target name
463            String sitePrefix = "";
464            if (OpenCms.getSiteManager().getSiteRoot(target) != null) {
465                String siteRootFolder = getCms().getRequestContext().getSiteRoot();
466                if (siteRootFolder.endsWith("/")) {
467                    siteRootFolder = siteRootFolder.substring(0, siteRootFolder.length() - 1);
468                }
469                sitePrefix = siteRootFolder;
470                storedSiteRoot = getCms().getRequestContext().getSiteRoot();
471                getCms().getRequestContext().setSiteRoot("/");
472            }
473
474            Iterator<String> i = getResourceList().iterator();
475            // iterate the resources to copy
476            while (i.hasNext()) {
477                String resName = i.next();
478                try {
479                    performSingleCopyOperation(resName, target, sitePrefix, copyMode, overwrite);
480                } catch (CmsException e) {
481                    if (isMultiOperation()) {
482                        // collect exceptions to create a detailed output
483                        addMultiOperationException(e);
484                    } else {
485                        // for single operation, throw the exception immediately
486                        throw e;
487                    }
488                }
489            }
490            // check if exceptions occurred
491            checkMultiOperationException(Messages.get(), Messages.ERR_COPY_MULTI_0);
492        } finally {
493            // restore the site root
494            if (storedSiteRoot != null) {
495                getCms().getRequestContext().setSiteRoot(storedSiteRoot);
496            }
497        }
498        return true;
499    }
500
501    /**
502     * Performs the copy operation for a single VFS resource.<p>
503     *
504     * @param source the source VFS path
505     * @param target the target VFS path
506     * @param sitePrefix the site prefix
507     * @param copyMode the copy mode for siblings
508     * @param overwrite the overwrite flag
509     *
510     * @throws CmsException if copying the resource fails
511     */
512    protected void performSingleCopyOperation(
513        String source,
514        String target,
515        String sitePrefix,
516        CmsResourceCopyMode copyMode,
517        boolean overwrite)
518    throws CmsException {
519
520        // calculate the target name
521        String finalTarget = CmsLinkManager.getAbsoluteUri(target, CmsResource.getParentFolder(source));
522
523        if (finalTarget.equals(source) || (isMultiOperation() && finalTarget.startsWith(source))) {
524            throw new CmsVfsException(Messages.get().container(Messages.ERR_COPY_ONTO_ITSELF_1, finalTarget));
525        }
526
527        try {
528            CmsResource res = getCms().readResource(finalTarget, CmsResourceFilter.ALL);
529            if (res.isFolder()) {
530                // target folder already exists, so we add the current folder name
531                if (!finalTarget.endsWith("/")) {
532                    finalTarget += "/";
533                }
534                finalTarget = finalTarget + CmsResource.getName(source);
535            }
536        } catch (CmsVfsResourceNotFoundException e) {
537            // target folder does not already exist, so target name is o.k.
538            if (LOG.isInfoEnabled()) {
539                LOG.info(e.getLocalizedMessage());
540            }
541        }
542
543        // set the target parameter value
544        setParamTarget(finalTarget);
545
546        // delete existing target resource if selected or confirmed by the user
547        if (overwrite && getCms().existsResource(finalTarget)) {
548            checkLock(finalTarget);
549            getCms().deleteResource(finalTarget, CmsResource.DELETE_PRESERVE_SIBLINGS);
550        }
551        // copy the resource
552        getCms().copyResource(sitePrefix + source, finalTarget, copyMode);
553    }
554}