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.UnknownAmetysObjectException; 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 return richText != null 247 ? richText.hasAttachment(infos.getFilename()) 248 ? CHECK.SUCCESS 249 : CHECK.NOT_FOUND 250 : CHECK.NOT_FOUND; 251 } 252 catch (Exception e) 253 { 254 throw new RuntimeException("Cannot check the uri '" + uri + "'", e); 255 } 256 } 257 258 @Override 259 public I18nizableText getLabel(String uri) 260 { 261 try 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 catch (UnknownAmetysObjectException e) 268 { 269 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_LOCAL_UNKNOWN"); 270 } 271 } 272 273 /** 274 * Helper class containg all infos parsed from URI. 275 */ 276 protected static class URIInfo 277 { 278 /** The content id. */ 279 private String _contentId; 280 /** The relevant attribute */ 281 private String _attribute; 282 /** The resource name */ 283 private String _filename; 284 /** The content version, if any. */ 285 private String _contentVersion; 286 /** The resolved content, if any. */ 287 private Content _content; 288 289 /** 290 * Returns the content id. 291 * @return the content id. 292 */ 293 public String getContentId() 294 { 295 return _contentId; 296 } 297 298 /** 299 * Set the content id. 300 * @param contentId the content id. 301 */ 302 public void setContentId(String contentId) 303 { 304 _contentId = contentId; 305 } 306 307 /** 308 * Returns the metadata. 309 * @return the metadata. 310 */ 311 public String getAttribute() 312 { 313 return _attribute; 314 } 315 316 /** 317 * Set the metadata. 318 * @param attribute the metadata. 319 */ 320 public void setAttribute(String attribute) 321 { 322 _attribute = attribute; 323 } 324 325 /** 326 * Returns the resource name. 327 * @return the name 328 */ 329 public String getFilename() 330 { 331 return _filename; 332 } 333 334 /** 335 * Set the resource name. 336 * @param name the name. 337 */ 338 public void setFilename(String name) 339 { 340 _filename = name; 341 } 342 343 /** 344 * Returns the content version, if any. 345 * @return the content version. 346 */ 347 public String getContentVersion() 348 { 349 return _contentVersion; 350 } 351 352 /** 353 * Set the content version. 354 * @param contentVersion the content version. 355 */ 356 public void setContentVersion(String contentVersion) 357 { 358 _contentVersion = contentVersion; 359 } 360 361 /** 362 * Returns the resolved content, if any. 363 * @return the content. 364 */ 365 public Content getContent() 366 { 367 return _content; 368 } 369 370 /** 371 * Set the content. 372 * @param content the content. 373 */ 374 public void setContent(Content content) 375 { 376 _content = content; 377 } 378 } 379}