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.core.util.ImageResolverHelper; 040import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 041import org.ametys.plugins.repository.model.RepositoryDataContext; 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 /** the context of the data being edited */ 057 protected DataContext _dataContext; 058 059 private URIPrefixHandler _prefixHandler; 060 061 @Override 062 public void service(ServiceManager manager) throws ServiceException 063 { 064 super.service(manager); 065 _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE); 066 } 067 068 @Override 069 public void startDocument() throws SAXException 070 { 071 Map objectModel = ContextHelper.getObjectModel(_context); 072 Map parentContextParameters = (Map) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 073 _dataContext = (DataContext) parentContextParameters.get("dataContext"); 074 075 super.startDocument(); 076 } 077 078 @Override 079 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException 080 { 081 // A new attachment starts being saxed 082 boolean isAttachment = _isAttachment(loc); 083 String type = attrs.getValue(__ATTACHMENT_TYPE_ATTRIBUTE_NAME); 084 if (isAttachment && __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE.equals(type)) 085 { 086 Attributes newAttrs = _processAttachment(attrs); 087 super.startElement(uri, loc, raw, newAttrs); 088 } 089 else 090 { 091 super.startElement(uri, loc, raw, attrs); 092 } 093 } 094 095 private boolean _isAttachment(String loc) 096 { 097 return __ATTACHMENT_IMAGE_TAG_NAME.equals(loc) || __ATTACHMENT_VIDEO_TAG_NAME.equals(loc) || __ATTACHMENT_AUDIO_TAG_NAME.equals(loc); 098 } 099 100 private Attributes _processAttachment(Attributes attrs) throws SAXException 101 { 102 RepositoryDataContext repoContext = _dataContext instanceof RepositoryDataContext rc ? rc : RepositoryDataContext.newInstance(_dataContext); 103 Content content = (Content) repoContext.getObject() 104 .orElseThrow(() -> new SAXException("The object id is required in the data context in order to set the fileref")); 105 106 String dataPath = _dataContext.getDataPath(); 107 RichText richText = content.getValue(dataPath); 108 109 String filename = attrs.getValue("fileref"); 110 Resource attachment = richText.getAttachment(filename); 111 112 AttributesImpl newAttrs = new AttributesImpl(attrs); 113 114 if (attachment != null) 115 { 116 _getAttachmentUUID(attachment) 117 .map(this::getRichTextByUUIDURL) 118 .ifPresentOrElse(url -> newAttrs.addCDATAAttribute("resourceByUUID", url), 119 () -> newAttrs.addCDATAAttribute("base64", _resolveAttachmentAsBase64(attrs, attachment))); 120 } 121 122 return newAttrs; 123 } 124 125 /** 126 * Retrieves the URL to retrieve a rich text attachment by its UUID 127 * @param uuid the UUID of the rich text's attachment 128 * @return the URL to retrieve a rich text attachment by its UUID 129 */ 130 protected String getRichTextByUUIDURL(String uuid) 131 { 132 StringBuilder resultPath = new StringBuilder(); 133 134 resultPath.append(_prefixHandler.computeUriPrefix(false, false)) 135 .append("/plugins/cms/richText-file-by-uuid/") 136 .append(uuid); 137 138 return resultPath.toString(); 139 } 140 141 private Optional<String> _getAttachmentUUID(Resource attachment) 142 { 143 return attachment.getRepositoryData() 144 .filter(JCRRepositoryData.class::isInstance) 145 .map(JCRRepositoryData.class::cast) 146 .map(JCRRepositoryData::getNode) 147 .map(this::_getNodeIdentifier); 148 } 149 150 private String _getNodeIdentifier(Node node) 151 { 152 try 153 { 154 return node.getIdentifier(); 155 } 156 catch (RepositoryException e) 157 { 158 // Return null to use the fallback method (base64) if we are not able to get the node identifier 159 return null; 160 } 161 } 162 163 private String _resolveAttachmentAsBase64(Attributes attrs, Resource attachment) 164 { 165 try (InputStream dataIs = attachment.getInputStream()) 166 { 167 String heightAsString = StringUtils.substringBefore(attrs.getValue("depth"), "px"); 168 int height = heightAsString != null ? Integer.valueOf(heightAsString) : -1; 169 170 String widthAsString = StringUtils.substringBefore(attrs.getValue("width"), "px"); 171 int width = widthAsString != null ? Integer.valueOf(widthAsString) : -1; 172 173 return ImageResolverHelper.resolveImageAsBase64(dataIs, attachment.getMimeType(), height, width, 0, 0); 174 } 175 catch (Exception e) 176 { 177 throw new IllegalStateException(e); 178 } 179 } 180}