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