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