001/* 002 * Copyright 2019 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.transformation; 017 018import java.io.InputStream; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Map; 022 023import javax.jcr.RepositoryException; 024import javax.jcr.Session; 025 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.logger.AbstractLogEnabled; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.cocoon.environment.Request; 035 036import org.ametys.cms.URIPrefixHandler; 037import org.ametys.cms.data.Resource; 038import org.ametys.cms.data.RichText; 039import org.ametys.cms.repository.Content; 040import org.ametys.cms.transformation.ConsistencyChecker.CHECK; 041import org.ametys.core.util.FilenameUtils; 042import org.ametys.core.util.URIUtils; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.repository.AmetysRepositoryException; 045import org.ametys.plugins.repository.data.UnknownDataException; 046import org.ametys.plugins.repository.version.VersionableAmetysObject; 047import org.ametys.runtime.i18n.I18nizableText; 048 049/** 050 * {@link URIResolver} for resources local to a rich text. 051 */ 052public class LocalURIResolver extends AbstractLogEnabled implements URIResolver, Contextualizable, Serviceable 053{ 054 /** The context */ 055 protected Context _context; 056 /** The ametys object resolver */ 057 protected AmetysObjectResolver _ametysObjectResolver; 058 059 private URIPrefixHandler _prefixHandler; 060 061 @Override 062 public void service(ServiceManager manager) throws ServiceException 063 { 064 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 065 _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE); 066 } 067 068 @Override 069 public void contextualize(Context context) throws ContextException 070 { 071 _context = context; 072 } 073 074 @Override 075 public String getType() 076 { 077 return "local"; 078 } 079 080 @Override 081 public String resolve(String uri, boolean download, boolean absolute, boolean internal) 082 { 083 URIInfo infos = getInfos(uri, false, null); 084 085 StringBuilder resultPath = new StringBuilder(); 086 087 resultPath.append(_prefixHandler.computeUriPrefix(absolute, internal)) 088 .append("/plugins/cms/richText-file/") 089 .append(FilenameUtils.encodeName(infos.getFilename())); 090 091 Map<String, String> params = new HashMap<>(); 092 params.put("contentId", infos.getContentId()); 093 params.put("attribute", infos.getAttribute()); 094 095 if (download) 096 { 097 params.put("download", "true"); 098 } 099 100 if (infos.getContentVersion() != null) 101 { 102 params.put("contentVersion", infos.getContentVersion()); 103 } 104 105 // Encode twice 106 return URIUtils.encodeURI(resultPath.toString(), params); 107 } 108 109 /** 110 * Parses the uri. 111 * @param uri the incoming uri. 112 * @param resolveContent true if the Content should be actually resolved if not found in the request. 113 * @param session the JCR {@link Session} to use, or null to use the current Session. 114 * @return an object containing all parsed infos. 115 */ 116 protected URIInfo getInfos(String uri, boolean resolveContent, Session session) 117 { 118 // uri are like <contentId>@<attribute>;<file.ext> 119 int i = uri.indexOf('@'); 120 int j = uri.indexOf(';', i); 121 String id = uri.substring(0, i); 122 String attribute = uri.substring(i + 1, j); 123 String filename = uri.substring(j + 1); 124 125 Content content = null; 126 127 try 128 { 129 Request request = ContextHelper.getRequest(_context); 130 content = (Content) request.getAttribute(Content.class.getName()); 131 } 132 catch (Exception e) 133 { 134 // there's no request, thus no "current" content 135 } 136 137 String contentVersion = null; 138 139 if (content == null || !id.equals(content.getId())) 140 { 141 // Some time (such as frontoffice edition) the image is rendered with no content in attribute 142 // The content should be resolved against the given id, but in that case, getRevision will be always the head on 143 try 144 { 145 content = resolveContent ? session == null ? _ametysObjectResolver.resolveById(id) : _ametysObjectResolver.resolveById(id, session) : null; 146 } 147 catch (RepositoryException e) 148 { 149 throw new AmetysRepositoryException(e); 150 } 151 } 152 else 153 { 154 if (content instanceof VersionableAmetysObject) 155 { 156 contentVersion = ((VersionableAmetysObject) content).getRevision(); 157 } 158 } 159 160 URIInfo infos = new URIInfo(); 161 infos.setContentId(id); 162 infos.setContentVersion(contentVersion); 163 infos.setContent(content); 164 infos.setAttribute(attribute); 165 infos.setFilename(filename); 166 167 return infos; 168 } 169 170 @Override 171 public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal) 172 { 173 return resolve(uri, download, absolute, internal); 174 } 175 176 @Override 177 public String resolveImageAsBase64(String uri, int height, int width) 178 { 179 return resolveImageAsBase64(uri, height, width, 0, 0, 0, 0); 180 } 181 182 @Override 183 public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal) 184 { 185 return resolve(uri, download, absolute, internal); 186 } 187 188 @Override 189 public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth) 190 { 191 return resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth, 0, 0); 192 } 193 194 @Override 195 public String resolveCroppedImage(String uri, int cropHeight, int cropWidth, boolean download, boolean absolute, boolean internal) 196 { 197 return resolve(uri, download, absolute, internal); 198 } 199 200 @Override 201 public String resolveCroppedImageAsBase64(String uri, int cropHeight, int cropWidth) 202 { 203 return resolveImageAsBase64(uri, 0, 0, 0, 0, cropHeight, cropWidth); 204 } 205 206 /** 207 * Get an image's bytes encoded as base64, optionally resized. 208 * @param uri the image URI. 209 * @param height the specified height. Ignored if negative. 210 * @param width the specified width. Ignored if negative. 211 * @param maxHeight the maximum image height. Ignored if height or width is specified. 212 * @param maxWidth the maximum image width. Ignored if height or width is specified. 213 * @param cropHeight The cropping height. Ignored if negative. 214 * @param cropWidth The cropping width. Ignored if negative. 215 * @return the image bytes encoded as base64. 216 */ 217 protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) 218 { 219 URIInfo infos = getInfos(uri, true, null); 220 221 try 222 { 223 RichText richText = infos.getContent().getValue(infos.getAttribute()); 224 Resource resource = richText.getAttachment(infos.getFilename()); 225 226 try (InputStream dataIs = resource.getInputStream()) 227 { 228 return ImageResolverHelper.resolveImageAsBase64(dataIs, resource.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth); 229 } 230 } 231 catch (Exception e) 232 { 233 throw new IllegalStateException(e); 234 } 235 } 236 237 @Override 238 public CHECK checkLink(String uri, boolean shortTest) 239 { 240 try 241 { 242 URIInfo infos = getInfos(uri, true, null); 243 244 Content content = infos.getContent(); 245 RichText richText = content.getValue(infos.getAttribute()); 246 richText.getAttachment(infos.getFilename()); 247 248 return CHECK.SUCCESS; 249 } 250 catch (UnknownDataException e) 251 { 252 return CHECK.NOT_FOUND; 253 } 254 catch (Exception e) 255 { 256 throw new RuntimeException("Cannot check the uri '" + uri + "'", e); 257 } 258 } 259 260 @Override 261 public I18nizableText getLabel(String uri) 262 { 263 URIInfo infos = getInfos(uri, false, null); 264 265 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_LOCAL_LABEL", Collections.singletonList(infos.getFilename())); 266 } 267 268 /** 269 * Helper class containg all infos parsed from URI. 270 */ 271 protected static class URIInfo 272 { 273 /** The content id. */ 274 private String _contentId; 275 /** The relevant attribute */ 276 private String _attribute; 277 /** The resource name */ 278 private String _filename; 279 /** The content version, if any. */ 280 private String _contentVersion; 281 /** The resolved content, if any. */ 282 private Content _content; 283 284 /** 285 * Returns the content id. 286 * @return the content id. 287 */ 288 public String getContentId() 289 { 290 return _contentId; 291 } 292 293 /** 294 * Set the content id. 295 * @param contentId the content id. 296 */ 297 public void setContentId(String contentId) 298 { 299 _contentId = contentId; 300 } 301 302 /** 303 * Returns the metadata. 304 * @return the metadata. 305 */ 306 public String getAttribute() 307 { 308 return _attribute; 309 } 310 311 /** 312 * Set the metadata. 313 * @param attribute the metadata. 314 */ 315 public void setAttribute(String attribute) 316 { 317 _attribute = attribute; 318 } 319 320 /** 321 * Returns the resource name. 322 * @return the name 323 */ 324 public String getFilename() 325 { 326 return _filename; 327 } 328 329 /** 330 * Set the resource name. 331 * @param name the name. 332 */ 333 public void setFilename(String name) 334 { 335 _filename = name; 336 } 337 338 /** 339 * Returns the content version, if any. 340 * @return the content version. 341 */ 342 public String getContentVersion() 343 { 344 return _contentVersion; 345 } 346 347 /** 348 * Set the content version. 349 * @param contentVersion the content version. 350 */ 351 public void setContentVersion(String contentVersion) 352 { 353 _contentVersion = contentVersion; 354 } 355 356 /** 357 * Returns the resolved content, if any. 358 * @return the content. 359 */ 360 public Content getContent() 361 { 362 return _content; 363 } 364 365 /** 366 * Set the content. 367 * @param content the content. 368 */ 369 public void setContent(Content content) 370 { 371 _content = content; 372 } 373 } 374}