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