001/* 002 * Copyright 2018 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.ugc.generators; 017 018import java.io.IOException; 019import java.net.MalformedURLException; 020import java.util.Arrays; 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.ProcessingException; 030import org.apache.cocoon.components.source.impl.SitemapSource; 031import org.apache.cocoon.environment.ObjectModelHelper; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.generation.ServiceableGenerator; 034import org.apache.cocoon.xml.AttributesImpl; 035import org.apache.cocoon.xml.XMLUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.excalibur.source.SourceResolver; 038import org.xml.sax.SAXException; 039 040import org.ametys.cms.CmsConstants; 041import org.ametys.cms.content.ContentHelper; 042import org.ametys.cms.contenttype.ContentType; 043import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 044import org.ametys.cms.repository.Content; 045import org.ametys.core.util.DateUtils; 046import org.ametys.core.util.IgnoreRootHandler; 047import org.ametys.plugins.repository.AmetysObjectResolver; 048import org.ametys.plugins.repository.UnknownAmetysObjectException; 049import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 050import org.ametys.plugins.repository.version.VersionableAmetysObject; 051import org.ametys.plugins.ugc.UGCConstants; 052import org.ametys.runtime.i18n.I18nizableText; 053import org.ametys.runtime.model.DefinitionContext; 054import org.ametys.web.WebConstants; 055import org.ametys.web.cache.PageHelper; 056import org.ametys.web.content.FOContentCreationHelper; 057import org.ametys.web.content.GetSiteAction; 058import org.ametys.web.repository.page.Page; 059import org.ametys.web.repository.page.ZoneItem; 060import org.ametys.web.skin.Skin; 061import org.ametys.web.skin.SkinsManager; 062import org.ametys.web.synchronization.SynchronizeComponent; 063 064/** 065 * UGC service generator 066 */ 067public class UGCGenerator extends ServiceableGenerator 068{ 069 /** The {@link ContentType} manager */ 070 protected ContentTypeExtensionPoint _cTypeExtPt; 071 /** The FO content creation helper */ 072 protected FOContentCreationHelper _foContentCreationHelper; 073 /** The page helper */ 074 protected PageHelper _pageHelper; 075 /** The content helper */ 076 protected ContentHelper _contentHelper; 077 /** The source resolver */ 078 protected SourceResolver _srcResolver; 079 /** The skin manager */ 080 protected SkinsManager _skinManager; 081 /** The content repository */ 082 protected Repository _repository; 083 /** The synchronize component */ 084 protected SynchronizeComponent _synchronizeComponent; 085 /** The ametys object resolver */ 086 protected AmetysObjectResolver _resolver; 087 088 @Override 089 public void service(ServiceManager smanager) throws ServiceException 090 { 091 super.service(smanager); 092 _cTypeExtPt = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 093 _foContentCreationHelper = (FOContentCreationHelper) smanager.lookup(FOContentCreationHelper.ROLE); 094 _pageHelper = (PageHelper) smanager.lookup(PageHelper.ROLE); 095 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 096 _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 097 _skinManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE); 098 _repository = (Repository) smanager.lookup(Repository.class.getName()); 099 _synchronizeComponent = (SynchronizeComponent) smanager.lookup(SynchronizeComponent.ROLE); 100 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 101 } 102 103 @Override 104 public void generate() throws IOException, SAXException, ProcessingException 105 { 106 Request request = ObjectModelHelper.getRequest(objectModel); 107 108 Page page = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE); 109 ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM); 110 111 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 112 String cType = (String) serviceParameters.getValue("content-type"); 113 if (StringUtils.isBlank(cType)) 114 { 115 throw new IllegalArgumentException("A content type must be defined"); 116 } 117 118 contentHandler.startDocument(); 119 120 XMLUtils.startElement(contentHandler, "ugc"); 121 122 DefinitionContext definitionContext = DefinitionContext.newInstance() 123 .withItemTagName("metadata"); 124 125 // SAX inputs for UGC mixin 126 XMLUtils.startElement(contentHandler, "mixin"); 127 ContentType ugcMixin = _cTypeExtPt.getExtension(UGCConstants.UGC_MIXIN_TYPE); 128 ugcMixin.getView("main").toSAX(contentHandler, definitionContext); 129 XMLUtils.endElement(contentHandler, "mixin"); 130 131 // SAX view 132 XMLUtils.startElement(contentHandler, "content-type"); 133 ContentType contentType = _cTypeExtPt.getExtension(cType); 134 contentType.getView("main").toSAX(contentHandler, definitionContext); 135 XMLUtils.endElement(contentHandler, "content-type"); 136 137 // SAX contents values for metadata of type CONTENT 138 _foContentCreationHelper.saxContentValues(contentHandler, contentType, "items", page.getSitemapName()); 139 140 XMLUtils.createElement(contentHandler, "has-captcha", String.valueOf(_pageHelper.isCaptchaRequired(page))); 141 142 _saxWarnings(zoneItem); 143 144 _saxGTUIfNeeded(zoneItem, request); 145 146 XMLUtils.endElement(contentHandler, "ugc"); 147 148 contentHandler.endDocument(); 149 } 150 151 private void _saxWarnings(ZoneItem zoneItem) throws SAXException 152 { 153 XMLUtils.startElement(contentHandler, "warnings"); 154 155 String gtuMode = zoneItem.getServiceParameters().getValue("general-terms-of-use-mode", false, "NONE"); 156 switch (gtuMode) 157 { 158 case "PAGE": 159 Optional<Page> gtuPage = _getGTUPage(zoneItem); 160 if (gtuPage.isEmpty() || !_isValid(gtuPage.get())) 161 { 162 XMLUtils.startElement(contentHandler, "warning"); 163 new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_UGC_ERROR_INVALID_GTU_PAGE").toSAX(contentHandler); 164 XMLUtils.endElement(contentHandler, "warning"); 165 } 166 break; 167 case "CONTENT": 168 Optional<Content> gtuContent = _getGTUContent(zoneItem); 169 if (gtuContent.isEmpty() || !_isValid(gtuContent.get())) 170 { 171 XMLUtils.startElement(contentHandler, "warning"); 172 new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_UGC_ERROR_INVALID_GTU_CONTENT").toSAX(contentHandler); 173 XMLUtils.endElement(contentHandler, "warning"); 174 } 175 break; 176 default: 177 // Nothing to check 178 } 179 180 XMLUtils.endElement(contentHandler, "warnings"); 181 } 182 183 private void _saxGTUIfNeeded(ZoneItem zoneItem, Request request) throws SAXException, MalformedURLException, IOException 184 { 185 String gtuMode = zoneItem.getServiceParameters().getValue("general-terms-of-use-mode", false, "NONE"); 186 if ("CONTENT".equals(gtuMode)) 187 { 188 Optional<Content> gtuContent = _getGTUContent(zoneItem); 189 if (gtuContent.isPresent()) 190 { 191 Content content = gtuContent.get(); 192 XMLUtils.startElement(contentHandler, "gtu"); 193 194 AttributesImpl attrs = new AttributesImpl(); 195 attrs.addCDATAAttribute("id", content.getId()); 196 attrs.addCDATAAttribute("name", content.getName()); 197 attrs.addCDATAAttribute("title", content.getTitle(null)); 198 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.zonedDateTimeToString(content.getLastModified())); 199 200 XMLUtils.startElement(contentHandler, "content", attrs); 201 202 String uri = _contentHelper.getContentHtmlViewUrl(content, "main"); 203 204 String currentSiteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME); 205 String currentSkinName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID); 206 request.setAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR, currentSiteName); 207 request.setAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR, currentSkinName); 208 209 SitemapSource src = null; 210 try 211 { 212 src = (SitemapSource) _srcResolver.resolveURI(uri); 213 src.toSAX(new IgnoreRootHandler(contentHandler)); 214 } 215 finally 216 { 217 _srcResolver.release(src); 218 request.removeAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR); 219 request.removeAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR); 220 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, currentSiteName); 221 request.setAttribute("siteName", currentSiteName); 222 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, currentSkinName); 223 } 224 XMLUtils.endElement(contentHandler, "content"); 225 226 XMLUtils.endElement(contentHandler, "gtu"); 227 } 228 } 229 } 230 231 private Optional<Page> _getGTUPage(ZoneItem zoneItem) 232 { 233 String gtuPageId = zoneItem.getServiceParameters().getValue("general-terms-of-use-page"); 234 if (StringUtils.isNotEmpty(gtuPageId)) 235 { 236 try 237 { 238 return Optional.of(_resolver.resolveById(gtuPageId)); 239 } 240 catch (UnknownAmetysObjectException e) 241 { 242 getLogger().warn("The general terms of use page with id '" + gtuPageId + "' does not exist anymore", e); 243 } 244 } 245 246 return Optional.empty(); 247 } 248 249 private Optional<Content> _getGTUContent(ZoneItem zoneItem) 250 { 251 String gtuContentId = zoneItem.getServiceParameters().getValue("general-terms-of-use-content"); 252 if (StringUtils.isNotEmpty(gtuContentId)) 253 { 254 try 255 { 256 return Optional.of(_resolver.resolveById(gtuContentId)); 257 } 258 catch (UnknownAmetysObjectException e) 259 { 260 getLogger().warn("The general terms of use content with id '" + gtuContentId + "' does not exist anymore", e); 261 } 262 } 263 264 return Optional.empty(); 265 } 266 267 private boolean _isValid(Page page) 268 { 269 Skin skin = _skinManager.getSkin(page.getSite().getSkinId()); 270 271 Session liveSession = null; 272 try 273 { 274 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 275 276 if (!_synchronizeComponent.isPageValid(page, skin) || !_synchronizeComponent.isHierarchyValid(page, liveSession)) 277 { 278 return false; 279 } 280 return true; 281 } 282 catch (RepositoryException e) 283 { 284 throw new RuntimeException("Unable to check live workspace", e); 285 } 286 finally 287 { 288 if (liveSession != null) 289 { 290 liveSession.logout(); 291 } 292 } 293 } 294 295 private boolean _isValid(Content content) 296 { 297 if (content instanceof VersionableAmetysObject vao) 298 { 299 return Arrays.asList(vao.getAllLabels()).contains(CmsConstants.LIVE_LABEL); 300 } 301 302 return false; 303 } 304}