001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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, 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.ade.postupload;
029
030import org.opencms.ade.configuration.CmsADEConfigData;
031import org.opencms.ade.postupload.shared.CmsPostUploadDialogBean;
032import org.opencms.ade.postupload.shared.CmsPostUploadDialogPanelBean;
033import org.opencms.ade.postupload.shared.I_CmsDialogConstants;
034import org.opencms.ade.postupload.shared.rpc.I_CmsPostUploadDialogService;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProperty;
037import org.opencms.file.CmsPropertyDefinition;
038import org.opencms.file.CmsResource;
039import org.opencms.file.types.CmsResourceTypeImage;
040import org.opencms.file.types.I_CmsResourceType;
041import org.opencms.flex.CmsFlexController;
042import org.opencms.gwt.CmsGwtService;
043import org.opencms.gwt.CmsPropertyEditorHelper;
044import org.opencms.gwt.CmsRpcException;
045import org.opencms.gwt.CmsVfsService;
046import org.opencms.gwt.shared.CmsListInfoBean;
047import org.opencms.gwt.shared.property.CmsClientProperty;
048import org.opencms.gwt.shared.property.CmsPropertyModification;
049import org.opencms.main.CmsException;
050import org.opencms.main.CmsLog;
051import org.opencms.main.CmsPermalinkResourceHandler;
052import org.opencms.main.OpenCms;
053import org.opencms.ui.dialogs.CmsGalleryOptimizeDialog;
054import org.opencms.util.CmsMacroResolver;
055import org.opencms.util.CmsStringUtil;
056import org.opencms.util.CmsUUID;
057import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
058import org.opencms.xml.content.CmsXmlContentProperty;
059import org.opencms.xml.content.CmsXmlContentPropertyHelper;
060
061import java.util.ArrayList;
062import java.util.HashSet;
063import java.util.LinkedHashMap;
064import java.util.LinkedHashSet;
065import java.util.List;
066import java.util.Locale;
067import java.util.Map;
068import java.util.Set;
069import java.util.stream.Collectors;
070
071import javax.servlet.http.HttpServletRequest;
072
073import org.apache.commons.logging.Log;
074
075import com.google.common.collect.Iterables;
076
077/**
078 * The service implementation for the org.opencms.ade.postupload module.<p>
079 */
080public class CmsPostUploadDialogService extends CmsGwtService implements I_CmsPostUploadDialogService {
081
082    /** Serial version id. */
083    private static final long serialVersionUID = 1L;
084
085    /** Logger instance for this class. */
086    private static final Log LOG = CmsLog.getLog(CmsPostUploadDialogService.class);
087
088    /**
089     * Creates a new instance.<p>
090     */
091    public CmsPostUploadDialogService() {
092
093        super();
094    }
095
096    /**
097     * Fetches the dialog data.<p>
098     *
099     * @param request the servlet request
100     *
101     * @return the dialog data
102     * @throws CmsRpcException if something goes wrong
103     */
104    public static CmsPostUploadDialogBean prefetch(HttpServletRequest request) throws CmsRpcException {
105
106        CmsPostUploadDialogService srv = new CmsPostUploadDialogService();
107        srv.setCms(CmsFlexController.getCmsObject(request));
108        srv.setRequest(request);
109        CmsPostUploadDialogBean result = null;
110        try {
111            result = srv.prefetch();
112        } finally {
113            srv.clearThreadStorage();
114        }
115        return result;
116    }
117
118    /**
119     * @see org.opencms.ade.postupload.shared.rpc.I_CmsPostUploadDialogService#load(org.opencms.util.CmsUUID, boolean,boolean)
120     */
121    public CmsPostUploadDialogPanelBean load(CmsUUID id, boolean useConfiguration, boolean addBasicProperties)
122    throws CmsRpcException {
123
124        try {
125            CmsResource res = getCmsObject().readResource(id);
126            List<CmsProperty> properties = getCmsObject().readPropertyObjects(res, false);
127            String title = CmsProperty.get(CmsPropertyDefinition.PROPERTY_TITLE, properties).getValue();
128            if (title == null) {
129                title = res.getName();
130            }
131            String description = CmsProperty.get(CmsPropertyDefinition.PROPERTY_DESCRIPTION, properties).getValue();
132            if (description == null) {
133                description = getCmsObject().getSitePath(res);
134            }
135            CmsListInfoBean listInfo = CmsVfsService.getPageInfo(getCmsObject(), res);
136
137            CmsPostUploadDialogPanelBean result = new CmsPostUploadDialogPanelBean(id, listInfo);
138            String warning = OpenCms.getADEManager().getUploadWarningTable().getMessage(res.getStructureId());
139            if (warning != null) {
140                Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject());
141                CmsMacroResolver resolver = new CmsMacroResolver();
142                resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale));
143                warning = resolver.resolveMacros(warning);
144                result.setWarning(warning);
145            }
146
147            CmsObject cms = getCmsObject();
148            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(res.getTypeId());
149            String typeName = type.getTypeName();
150            listInfo.setResourceType(typeName);
151
152            CmsExplorerTypeSettings settings = OpenCms.getWorkplaceManager().getExplorerTypeSetting(typeName);
153
154            List<String> defaultProperties = settings.getProperties();
155            while (properties.isEmpty() && !CmsStringUtil.isEmptyOrWhitespaceOnly(settings.getReference())) {
156                settings = OpenCms.getWorkplaceManager().getExplorerTypeSetting(settings.getReference());
157                defaultProperties = settings.getProperties();
158            }
159
160            Map<String, CmsXmlContentProperty> propertyDefinitions = new LinkedHashMap<String, CmsXmlContentProperty>();
161            Map<String, CmsClientProperty> clientProperties = new LinkedHashMap<String, CmsClientProperty>();
162
163            // match strings consisting of one or more alphanumeric characters and those from NAME_CONSTRAINTS, but exclude those that are just sequences of one or more "."s
164            String regex = "^(?!\\.+$)[" + CmsResource.NAME_CONSTRAINTS + "a-zA-Z0-9]+$";
165            Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
166            String validationMessage = Messages.get().getBundle(locale).key(
167                Messages.GUI_POSTUPLOAD_FILENAME_VALIDATION_ERROR_1,
168                CmsResource.NAME_CONSTRAINTS);
169            CmsXmlContentProperty fileNamePropDef = new CmsXmlContentProperty(
170                CmsPropertyModification.FILE_NAME_PROPERTY,
171                "string",
172                "string",
173                "",
174                regex,
175                "",
176                "",
177                Messages.get().getBundle(OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject())).key(
178                    Messages.GUI_UPLOAD_FILE_NAME_0),
179                "",
180                validationMessage,
181                "false");
182            propertyDefinitions.put(CmsPropertyModification.FILE_NAME_PROPERTY, fileNamePropDef);
183            clientProperties.put(
184                CmsPropertyModification.FILE_NAME_PROPERTY,
185                new CmsClientProperty(CmsPropertyModification.FILE_NAME_PROPERTY, res.getName(), res.getName()));
186
187            CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration(
188                getCmsObject(),
189                res.getRootPath());
190            Map<String, CmsXmlContentProperty> propertyConfiguration = configData.getPropertyConfigurationAsMap();
191
192            Set<String> propertiesToShow = new LinkedHashSet<String>();
193            propertiesToShow.addAll(defaultProperties);
194            if (addBasicProperties) {
195                propertiesToShow.addAll(propertyConfiguration.keySet());
196            }
197            Set<String> requiredProperties = getRequiredProperties(getCmsObject(), res);
198            for (String propertyName : propertiesToShow) {
199                CmsXmlContentProperty propDef = null;
200                if (useConfiguration) {
201                    propDef = propertyConfiguration.get(propertyName);
202                }
203                if (propDef == null) {
204                    propDef = new CmsXmlContentProperty(
205                        propertyName,
206                        "string",
207                        "string",
208                        "",
209                        "",
210                        "",
211                        "",
212                        null,
213                        "",
214                        "",
215                        "false");
216                }
217                if (requiredProperties.contains(propertyName)) {
218                    String validationErrorMessage = Messages.get().getBundle(
219                        OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject())).key(
220                            Messages.GUI_POSTUPLOAD_REQUIRED_PROPERTY_1,
221                            propertyName);
222                    propDef = propDef.withValidation(".*?[^ ].*", "error", validationErrorMessage);
223                }
224
225                propertyDefinitions.put(propertyName, propDef);
226                CmsProperty property = CmsProperty.get(propertyName, properties);
227                if (property != null) {
228                    CmsClientProperty clientProperty = new CmsClientProperty(
229                        propertyName,
230                        property.getStructureValue(),
231                        property.getResourceValue());
232                    clientProperties.put(clientProperty.getName(), clientProperty);
233                }
234            }
235
236            propertyDefinitions = CmsXmlContentPropertyHelper.resolveMacrosInProperties(
237                propertyDefinitions,
238                CmsMacroResolver.newWorkplaceLocaleResolver(getCmsObject()));
239
240            CmsPropertyEditorHelper.updateWysiwygConfig(propertyDefinitions, getCmsObject(), res);
241
242            String previewLink = null;
243            if (CmsResourceTypeImage.getStaticTypeName().equals(typeName)) {
244                String extension = CmsResource.getExtension(res.getRootPath());
245                String suffix = extension != null ? "." + extension : "";
246                String permalink = CmsStringUtil.joinPaths(
247                    OpenCms.getSystemInfo().getOpenCmsContext(),
248                    CmsPermalinkResourceHandler.PERMALINK_HANDLER,
249                    res.getStructureId().toString()) + suffix;
250                previewLink = permalink + CmsGalleryOptimizeDialog.getScaleQueryString(false);
251                result.setPermalink(permalink);
252                result.setPreviewLink(previewLink);
253                result.setHighResPreviewLink(permalink + CmsGalleryOptimizeDialog.getScaleQueryString(true));
254                result.setPreviewInfo1((res.getLength() / 1024) + "kb");
255                CmsProperty imageSizeProp = cms.readPropertyObject(
256                    res,
257                    CmsPropertyDefinition.PROPERTY_IMAGE_SIZE,
258                    false);
259                String imageSizeText = "? x ?";
260                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(imageSizeProp.getValue())) {
261                    Map<String, String> imageSizeAttrs = CmsStringUtil.splitAsMap(imageSizeProp.getValue(), ",", ":");
262                    String w = imageSizeAttrs.get("w");
263                    String h = imageSizeAttrs.get("h");
264                    if ((w != null) && (h != null)) {
265                        imageSizeText = w + " x " + h;
266
267                    }
268                }
269                result.setPreviewInfo2(imageSizeText);
270            }
271
272            result.setPropertyDefinitions(propertyDefinitions);
273            result.setProperties(clientProperties);
274            return result;
275        } catch (CmsException e) {
276            error(e);
277            return null; // will never be reached
278        }
279    }
280
281    /**
282     * @see org.opencms.ade.postupload.shared.rpc.I_CmsPostUploadDialogService#prefetch()
283     */
284    public CmsPostUploadDialogBean prefetch() throws CmsRpcException {
285
286        try {
287
288            List<CmsResource> resources = new ArrayList<>();
289
290            if ((CmsStringUtil.isNotEmptyOrWhitespaceOnly(
291                getRequest().getParameter(I_CmsDialogConstants.PARAM_RESOURCES)))) {
292                // if the request parameter resources exists and contains a list of UUIDs
293                // this dialog is used as upload hook
294                String resourcesParam = getRequest().getParameter(I_CmsDialogConstants.PARAM_RESOURCES);
295                List<String> resourceUUIDs = CmsStringUtil.splitAsList(resourcesParam, ",");
296                for (String uuidAsString : resourceUUIDs) {
297                    CmsUUID uuid = new CmsUUID(uuidAsString);
298                    CmsResource res = getCmsObject().readResource(uuid);
299                    resources.add(res);
300                }
301            } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getRequest().getParameter("resource"))) {
302                // if there was no parameter "resources" set as request parameter
303                // this dialog is not used as upload hook try to read the resource parameter
304                String resourceParam = getRequest().getParameter("resource");
305                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(resourceParam)) {
306                    CmsResource res = getCmsObject().readResource(resourceParam);
307                    resources.add(res);
308                }
309            }
310            return createUploadDialogBean(resources);
311        } catch (CmsException e) {
312            error(e);
313            return null; // will never be reached
314        }
315    }
316
317    /**
318     * Creates the data bean for the dialog from the list of created resources.
319     *
320     * @param resources the resources
321     * @return the data bean for the dialog
322     */
323    private CmsPostUploadDialogBean createUploadDialogBean(List<CmsResource> resources) {
324
325        Map<CmsUUID, String> result = new LinkedHashMap<>();
326        CmsObject cms = getCmsObject();
327        boolean hasImage = false;
328        for (CmsResource resource : resources) {
329            if (OpenCms.getResourceManager().matchResourceType(
330                CmsResourceTypeImage.getStaticTypeName(),
331                resource.getTypeId())) {
332                hasImage = true;
333                break;
334            }
335        }
336        // split resource list into two parts, ones that have required properties and ones that don't,
337        // then iterate over the ones with required properties first.
338        //
339        // this is because the buttons in the upload property dialog only trigger validation for the current tab,
340        // so we want the user to go through all resources which require validation first before they can exit the dialog.
341        Map<Boolean, List<CmsResource>> parts = resources.stream().collect(
342            Collectors.partitioningBy(res -> getRequiredProperties(cms, res).size() > 0));
343
344        for (CmsResource res : Iterables.concat(parts.get(Boolean.TRUE), parts.get(Boolean.FALSE))) {
345            result.put(res.getStructureId(), cms.getRequestContext().removeSiteRoot(res.getRootPath()));
346        }
347        Set<CmsUUID> reqValIds = parts.get(Boolean.TRUE).stream().map(res -> res.getStructureId()).collect(
348            Collectors.toSet());
349        return new CmsPostUploadDialogBean(result, reqValIds, hasImage);
350    }
351
352    /**
353     * Gets the properties required for the given resource (as defined by the requiredOnUpload setting on the corresponding explorertype).
354     *
355     * @param cms the CMS context
356     * @param res a resource
357     * @return the set of required properties
358     */
359    private Set<String> getRequiredProperties(CmsObject cms, CmsResource res) {
360
361        Set<String> requiredProps = new HashSet<>();
362        try {
363            String typeName = OpenCms.getResourceManager().getResourceType(res).getTypeName();
364            CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(typeName);
365            if (explorerType != null) {
366                for (String prop : explorerType.getProperties()) {
367                    if (explorerType.isPropertyRequiredOnUpload(prop)) {
368                        requiredProps.add(prop);
369                    }
370                }
371            }
372        } catch (Exception e) {
373            LOG.error(e.getLocalizedMessage(), e);
374            return requiredProps;
375        }
376        return requiredProps;
377    }
378
379}