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}