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}