001/* 002 * Copyright 2021 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 */ 016 017package org.ametys.cms.transformation.htmledition; 018 019import java.io.InputStream; 020import java.util.Map; 021import java.util.Optional; 022 023import javax.jcr.Node; 024import javax.jcr.RepositoryException; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.components.ContextHelper; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.xml.AttributesImpl; 031import org.apache.commons.lang3.StringUtils; 032import org.xml.sax.Attributes; 033import org.xml.sax.SAXException; 034 035import org.ametys.cms.URIPrefixHandler; 036import org.ametys.cms.data.Resource; 037import org.ametys.cms.data.RichText; 038import org.ametys.cms.repository.Content; 039import org.ametys.cms.transformation.ImageResolverHelper; 040import org.ametys.plugins.repository.AmetysObjectResolver; 041import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 042import org.ametys.runtime.model.type.DataContext; 043 044/** 045 * Handler to enhance the rich text attachments. 046 * Add the content id and the data path to the local attachment's attribute 047 */ 048public class DocbookLocalMediaObjectEditionHandler extends AbstractHTMLEditionHandler 049{ 050 private static final String __ATTACHMENT_IMAGE_TAG_NAME = "imagedata"; 051 private static final String __ATTACHMENT_VIDEO_TAG_NAME = "videodata"; 052 private static final String __ATTACHMENT_AUDIO_TAG_NAME = "audiodata"; 053 private static final String __ATTACHMENT_TYPE_ATTRIBUTE_NAME = "type"; 054 private static final String __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE = "local"; 055 056 /** Ametys object resolver */ 057 protected AmetysObjectResolver _resolver; 058 059 /** the context of the data being edited */ 060 protected DataContext _dataContext; 061 062 private URIPrefixHandler _prefixHandler; 063 064 @Override 065 public void service(ServiceManager manager) throws ServiceException 066 { 067 super.service(manager); 068 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 069 _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE); 070 } 071 072 @Override 073 public void startDocument() throws SAXException 074 { 075 Map objectModel = ContextHelper.getObjectModel(_context); 076 Map parentContextParameters = (Map) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 077 _dataContext = (DataContext) parentContextParameters.get("dataContext"); 078 079 super.startDocument(); 080 } 081 082 @Override 083 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException 084 { 085 // A new attachment starts being saxed 086 boolean isAttachment = _isAttachment(loc); 087 String type = attrs.getValue(__ATTACHMENT_TYPE_ATTRIBUTE_NAME); 088 if (isAttachment && __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE.equals(type)) 089 { 090 Attributes newAttrs = _processAttachment(attrs); 091 super.startElement(uri, loc, raw, newAttrs); 092 } 093 else 094 { 095 super.startElement(uri, loc, raw, attrs); 096 } 097 } 098 099 private boolean _isAttachment(String loc) 100 { 101 return __ATTACHMENT_IMAGE_TAG_NAME.equals(loc) || __ATTACHMENT_VIDEO_TAG_NAME.equals(loc) || __ATTACHMENT_AUDIO_TAG_NAME.equals(loc); 102 } 103 104 private Attributes _processAttachment(Attributes attrs) throws SAXException 105 { 106 String contentId = _dataContext.getObjectId() 107 .orElseThrow(() -> new SAXException("The object id is required in the data context in order to set the fileref")); 108 Content content = _resolver.resolveById(contentId); 109 110 String dataPath = _dataContext.getDataPath(); 111 RichText richText = content.getValue(dataPath); 112 113 String filename = attrs.getValue("fileref"); 114 Resource attachment = richText.getAttachment(filename); 115 116 AttributesImpl newAttrs = new AttributesImpl(attrs); 117 118 if (attachment != null) 119 { 120 _getAttachmentUUID(attachment) 121 .map(this::getRichTextByUUIDURL) 122 .ifPresentOrElse(url -> newAttrs.addCDATAAttribute("resourceByUUID", url), 123 () -> newAttrs.addCDATAAttribute("base64", _resolveAttachmentAsBase64(attrs, attachment))); 124 } 125 126 return newAttrs; 127 } 128 129 /** 130 * Retrieves the URL to retrieve a rich text attachment by its UUID 131 * @param uuid the UUID of the rich text's attachment 132 * @return the URL to retrieve a rich text attachment by its UUID 133 */ 134 protected String getRichTextByUUIDURL(String uuid) 135 { 136 StringBuilder resultPath = new StringBuilder(); 137 138 resultPath.append(_prefixHandler.computeUriPrefix(false, false)) 139 .append("/plugins/cms/richText-file-by-uuid/") 140 .append(uuid); 141 142 return resultPath.toString(); 143 } 144 145 private Optional<String> _getAttachmentUUID(Resource attachment) 146 { 147 return attachment.getRepositoryData() 148 .filter(JCRRepositoryData.class::isInstance) 149 .map(JCRRepositoryData.class::cast) 150 .map(JCRRepositoryData::getNode) 151 .map(this::_getNodeIdentifier); 152 } 153 154 private String _getNodeIdentifier(Node node) 155 { 156 try 157 { 158 return node.getIdentifier(); 159 } 160 catch (RepositoryException e) 161 { 162 // Return null to use the fallback method (base64) if we are not able to get the node identifier 163 return null; 164 } 165 } 166 167 private String _resolveAttachmentAsBase64(Attributes attrs, Resource attachment) 168 { 169 try (InputStream dataIs = attachment.getInputStream()) 170 { 171 String heightAsString = StringUtils.substringBefore(attrs.getValue("depth"), "px"); 172 int height = heightAsString != null ? Integer.valueOf(heightAsString) : -1; 173 174 String widthAsString = StringUtils.substringBefore(attrs.getValue("width"), "px"); 175 int width = widthAsString != null ? Integer.valueOf(widthAsString) : -1; 176 177 return ImageResolverHelper.resolveImageAsBase64(dataIs, attachment.getMimeType(), height, width, 0, 0); 178 } 179 catch (Exception e) 180 { 181 throw new IllegalStateException(e); 182 } 183 } 184}