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.plugins.newsletter.testsending; 017 018import java.io.InputStream; 019import java.util.HashMap; 020import java.util.Map; 021import java.util.Optional; 022 023import javax.jcr.Repository; 024import javax.jcr.RepositoryException; 025import javax.jcr.Session; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.components.ContextHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.commons.lang3.StringUtils; 032 033import org.ametys.cms.repository.Content; 034import org.ametys.cms.transformation.ImageResolverHelper; 035import org.ametys.core.util.FilenameUtils; 036import org.ametys.core.util.URIUtils; 037import org.ametys.plugins.repository.metadata.File; 038import org.ametys.plugins.repository.metadata.Resource; 039import org.ametys.plugins.repository.metadata.RichText; 040import org.ametys.plugins.repository.version.VersionableAmetysObject; 041import org.ametys.web.WebConstants; 042import org.ametys.web.WebHelper; 043import org.ametys.web.editor.LocalURIResolver; 044 045/** 046 * Resolver for local uri in newsletters 047 */ 048public class NewsletterLocalURIResolver extends LocalURIResolver 049{ 050 /** resolver data type for newsletter local */ 051 public static final String NEWSLETTER_LOCAL_DATA_TYPE = "newsletter-local"; 052 private Repository _repository; 053 054 @Override 055 public void service(ServiceManager manager) throws ServiceException 056 { 057 super.service(manager); 058 _repository = (Repository) manager.lookup(Repository.class.getName()); 059 } 060 061 @Override 062 public String getType() 063 { 064 return NEWSLETTER_LOCAL_DATA_TYPE; 065 } 066 067 @Override 068 public String resolve(String uri, boolean download, boolean absolute, boolean internal) 069 { 070 return _resolve(uri, download, absolute, internal, "_contents", null); 071 } 072 073 @Override 074 public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal) 075 { 076 String suffix = height != 0 || width != 0 ? "_" + height + "x" + width : null; 077 return _resolve(uri, download, absolute, internal, "_contents-images", suffix); 078 } 079 080 @Override 081 public String resolveImageAsBase64(String uri, int height, int width) 082 { 083 return _resolveImageAsBase64(uri, height, width, 0, 0, 0, 0); 084 } 085 086 @Override 087 public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal) 088 { 089 String suffix = maxHeight != 0 || maxWidth != 0 ? "_max" + maxHeight + "x" + maxWidth : null; 090 return _resolve(uri, download, absolute, internal, "_contents-images", suffix); 091 } 092 093 @Override 094 public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth) 095 { 096 return _resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth, 0, 0); 097 } 098 099 @Override 100 public String resolveCroppedImage(String uri, int cropHeight, int cropWidth, boolean download, boolean absolute, boolean internal) 101 { 102 String suffix = cropHeight != 0 || cropWidth != 0 ? "_crop" + cropHeight + "x" + cropWidth : null; 103 return _resolve(uri, download, absolute, internal, "_contents-images", suffix); 104 } 105 106 @Override 107 public String resolveCroppedImageAsBase64(String uri, int cropHeight, int cropWidth) 108 { 109 return _resolveImageAsBase64(uri, 0, 0, 0, 0, cropHeight, cropWidth); 110 } 111 112 /** 113 * Resolves a link URI for rendering purposes.<br> 114 * The output must be a properly encoded path, relative to the webapp context, accessible from a browser. 115 * @param uri the link URI. 116 * @param download true if the pointed resource is to be downloaded. 117 * @param absolute true if the url must be absolute 118 * @param internal true to get an internal URI. 119 * @param pipeline The pipeline to use to server the content 120 * @param suffix The suffix to add to the resolved path 121 * @return the path to the resource. 122 */ 123 protected String _resolve(String uri, boolean download, boolean absolute, boolean internal, String pipeline, String suffix) 124 { 125 // uri are like content://UUID@metadata;data/file.ext 126 int i = uri.indexOf('@'); 127 int j = uri.indexOf(';', i); 128 String id = uri.substring(0, i); 129 String metadata = uri.substring(i + 1, j); 130 String path = uri.substring(j + 1); 131 132 Request request = ContextHelper.getRequest(_context); 133 134 Session liveSession = null; 135 try 136 { 137 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 138 Content content = _getContent(id, request, liveSession); 139 140 RichText richText = _getMeta(content.getMetadataHolder(), metadata); 141 File file = _getFile(richText.getAdditionalDataFolder(), path); 142 if (file == null) 143 { 144 getLogger().warn("Cannot resolve link " + uri); 145 return ""; 146 } 147 148 StringBuilder resultPath = new StringBuilder(); 149 resultPath.append(_getUriPrefix(request, content, download, absolute, internal)) 150 .append("/") 151 .append(pipeline) 152 .append(FilenameUtils.encodePath(content.getPath())) 153 .append("/_metadata/") 154 .append(metadata) 155 .append("/_data/") 156 .append(FilenameUtils.encodePath(path)); 157 158 if (StringUtils.isNotEmpty(suffix)) 159 { 160 resultPath.append(suffix); 161 } 162 163 Map<String, String> params = new HashMap<>(); 164 if (download) 165 { 166 params.put("download", "true"); 167 } 168 169 Optional.of(content) 170 .filter(VersionableAmetysObject.class::isInstance) 171 .map(VersionableAmetysObject.class::cast) 172 .map(VersionableAmetysObject::getRevision) 173 .ifPresent(contentVersion -> params.put("contentVersion", contentVersion)); 174 175 return URIUtils.encodeURI(resultPath.toString(), params); 176 } 177 catch (Exception e) 178 { 179 throw new IllegalStateException(e); 180 } 181 finally 182 { 183 if (liveSession != null) 184 { 185 liveSession.logout(); 186 } 187 } 188 } 189 190 /** 191 * Retrieve the content, using the request attribute to avoid an additional resolve 192 * @param id The content id 193 * @param request The request 194 * @param session The session into which the content should be retrieved 195 * @return The content 196 * @throws RepositoryException If an error occurred 197 */ 198 protected Content _getContent(String id, Request request, Session session) throws RepositoryException 199 { 200 Content content = (Content) request.getAttribute(Content.class.getName()); 201 if (content == null || !id.equals(content.getId())) 202 { 203 content = _ametysObjectResolver.resolveById(id, session); 204 } 205 return content; 206 } 207 208 /** 209 * Resolve image as base 64 210 * @param uri the link URI. 211 * @param height the specified height. Ignored if 0. 212 * @param width the specified width. Ignored if 0. 213 * @param maxHeight the maximum image height. Ignored if height or width is specified. 214 * @param maxWidth the maximum image width. Ignored if height or width is specified. 215 * @param cropHeight the cropping height. Ignored if 0. 216 * @param cropWidth the cropping width. Ignored if 0. 217 * @return a base64-encoded string representing the image. 218 */ 219 protected String _resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) 220 { 221 // uri are like content://UUID@metadata;data/file.ext 222 int i = uri.indexOf('@'); 223 int j = uri.indexOf(';', i); 224 String id = uri.substring(0, i); 225 String metadata = uri.substring(i + 1, j); 226 String path = uri.substring(j + 1); 227 228 Request request = ContextHelper.getRequest(_context); 229 230 Session liveSession = null; 231 try 232 { 233 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 234 235 Content content = _getContent(id, request, liveSession); 236 RichText richText = _getMeta(content.getMetadataHolder(), metadata); 237 File file = _getFile(richText.getAdditionalDataFolder(), path); 238 Resource resource = file.getResource(); 239 240 try (InputStream dataIs = resource.getInputStream()) 241 { 242 return ImageResolverHelper.resolveImageAsBase64(dataIs, resource.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth); 243 } 244 } 245 catch (Exception e) 246 { 247 throw new IllegalStateException(e); 248 } 249 finally 250 { 251 if (liveSession != null) 252 { 253 liveSession.logout(); 254 } 255 } 256 } 257 258 private String _getUriPrefix(Request request, Content content, boolean download, boolean absolute, boolean internal) 259 { 260 String siteName = WebHelper.getSiteName(request, content); 261 if (StringUtils.isEmpty(siteName)) 262 { 263 return getUriPrefix(download, absolute, internal); 264 } 265 266 if (internal) 267 { 268 return "cocoon://" + siteName; 269 } 270 else if (absolute) 271 { 272 return _prefixHandler.getAbsoluteUriPrefix(siteName); 273 } 274 else 275 { 276 return _prefixHandler.getUriPrefix(siteName); 277 } 278 } 279}