001/*
002 *  Copyright 2012 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.web.usermanagement;
017
018import java.io.IOException;
019import java.net.MalformedURLException;
020import java.util.Arrays;
021import java.util.Collection;
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.repository.Content;
041import org.ametys.core.user.CurrentUserProvider;
042import org.ametys.core.user.UserIdentity;
043import org.ametys.core.util.DateUtils;
044import org.ametys.core.util.IgnoreRootHandler;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.repository.UnknownAmetysObjectException;
047import org.ametys.plugins.repository.version.VersionableAmetysObject;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.web.WebConstants;
050import org.ametys.web.cache.PageHelper;
051import org.ametys.web.repository.page.Page;
052import org.ametys.web.repository.page.ZoneItem;
053import org.ametys.web.repository.site.Site;
054import org.ametys.web.repository.site.SiteManager;
055import org.ametys.web.skin.Skin;
056import org.ametys.web.skin.SkinsManager;
057import org.ametys.web.synchronization.SynchronizeComponent;
058
059import com.google.common.collect.Multimap;
060
061/**
062 * Generate information to render the user signup service.
063 */
064public class UserSignupGenerator extends ServiceableGenerator
065{
066    /** The user signup manager. */
067    protected UserSignupManager _userSignupManager;
068    /** The site Manager. */
069    protected SiteManager _siteManager;
070    /** The ametys object resolver. */
071    protected AmetysObjectResolver _resolver;
072    /** The component for live synchronization */
073    protected SynchronizeComponent _synchronizeComponent;
074    /** The {@link SkinsManager}*/
075    protected SkinsManager _skinManager;
076    /** The repository */
077    protected Repository _repository;
078    /** Page helper */
079    protected PageHelper _pageHelper;
080    /** The current user provider */
081    protected CurrentUserProvider _currentUserProvider;
082    /** The source resolver */
083    protected SourceResolver _srcResolver;
084    
085    @Override
086    public void service(ServiceManager serviceManager) throws ServiceException
087    {
088        super.service(serviceManager);
089        _userSignupManager = (UserSignupManager) serviceManager.lookup(UserSignupManager.ROLE);
090        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
091        _srcResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE);
092        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
093        _skinManager = (SkinsManager) serviceManager.lookup(SkinsManager.ROLE);
094        _synchronizeComponent = (SynchronizeComponent) serviceManager.lookup(SynchronizeComponent.ROLE);
095        _repository = (Repository) serviceManager.lookup(Repository.class.getName());
096        _pageHelper = (PageHelper) serviceManager.lookup(PageHelper.ROLE);
097        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
098    }
099    
100    @Override
101    public void generate() throws IOException, SAXException, ProcessingException
102    {
103        Request request = ObjectModelHelper.getRequest(objectModel);
104        String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME);
105        String language = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME);
106        Page page = (Page) request.getAttribute(Page.class.getName());
107        ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM);
108        Site site = _siteManager.getSite(siteName);
109        
110        Boolean publicSignup = site.getValue("public-signup");
111        Page signupPage = _userSignupManager.getSignupPage(siteName, language);
112        Page pwdChangePage = _userSignupManager.getPwdChangePage(siteName, language);
113        
114        @SuppressWarnings("unchecked")
115        Multimap<String, I18nizableText> errors = (Multimap<String, I18nizableText>) request.getAttribute("errors");
116        String firstName = request.getParameter("firstname");
117        String lastName = request.getParameter("lastname");
118        String email = request.getParameter("email");
119        String token = request.getParameter("token");
120        
121        contentHandler.startDocument();
122        
123        AttributesImpl attrs = new AttributesImpl();
124        if (page != null)
125        {
126            _addNotEmptyAttribute (attrs, "current-page", page.getId());
127        }
128        if (publicSignup != null)
129        {
130            _addNotEmptyAttribute (attrs, "public-signup", publicSignup.toString());
131        }
132        if (signupPage != null)
133        {
134            _addNotEmptyAttribute (attrs, "signup-page-id", signupPage.getId());
135        }
136        if (pwdChangePage != null)
137        {
138            _addNotEmptyAttribute (attrs, "password-change-page-id", pwdChangePage.getId());
139        }
140        
141        UserIdentity userIdentity = _currentUserProvider.getUser();
142        if (userIdentity != null)
143        {
144            _addNotEmptyAttribute (attrs, "fo-user-login", userIdentity.getLogin());
145            _addNotEmptyAttribute (attrs, "fo-user-population", userIdentity.getPopulationId());
146        }
147        
148        // Keep for compatibity purpose but already sax in #saxInputs
149        _addNotEmptyAttribute (attrs, "firstname", firstName);
150        _addNotEmptyAttribute (attrs, "lastname", lastName);
151        _addNotEmptyAttribute (attrs, "email", email);
152        _addNotEmptyAttribute (attrs, "token", token);
153        
154        XMLUtils.startElement(contentHandler, "user-signup", attrs);
155        
156        if (errors != null)
157        {
158            saxErrors(errors);
159        }
160        
161        Page tosPage = _userSignupManager.getGTUPage(zoneItem);
162        Page successPage = _userSignupManager.getSuccessPage(zoneItem);
163        Content tosContent = _userSignupManager.getGTUContent(zoneItem);
164        Content successContent = _userSignupManager.getSuccessContent(zoneItem);
165        
166        XMLUtils.startElement(contentHandler, "user-inputs");
167        saxUserInputs(request, zoneItem);
168        XMLUtils.endElement(contentHandler, "user-inputs");
169        
170        saxWarnings(publicSignup, signupPage, pwdChangePage, tosPage, successPage, tosContent, successContent);
171        
172        XMLUtils.createElement(contentHandler, "has-captcha", String.valueOf(_pageHelper.isCaptchaRequired(page)));
173        
174        saxTOSIfNeeded(zoneItem);
175        saxSuccessContentIfNeeded(zoneItem);
176        
177        saxAdditionalInformation(siteName, language, zoneItem);
178        
179        XMLUtils.endElement(contentHandler, "user-signup");
180        
181        contentHandler.endDocument();
182    }
183    
184    /**
185     * SAX the user inputs
186     * @param request the request
187     * @param zoneItem the zone item holding the service
188     * @throws SAXException if an error occurs
189     */
190    protected void saxUserInputs(Request request, ZoneItem zoneItem) throws SAXException
191    {
192        _saxNotEmptyParameter(request, "firstname");
193        _saxNotEmptyParameter(request, "lastname");
194        _saxNotEmptyParameter(request, "email");
195        _saxNotEmptyParameter(request, "token");
196    }
197    
198    /**
199     * Sax a non-empty request parameter
200     * @param request the request
201     * @param paramName the parameter name
202     * @throws SAXException  if an error occurs
203     */
204    protected void _saxNotEmptyParameter(Request request, String paramName) throws SAXException
205    {
206        _saxNotEmptyValue(paramName, request.getParameter(paramName));
207    }
208    
209    /**
210     * Sax a non-empty value
211     * @param tagName the tag name
212     * @param value the value
213     * @throws SAXException  if an error occurs
214     */
215    protected void _saxNotEmptyValue(String tagName, String value) throws SAXException
216    {
217        if (StringUtils.isNotEmpty(value))
218        {
219            XMLUtils.createElement(contentHandler, tagName, value);
220        }
221    }
222    
223    /**
224     * SAX additional information
225     * @param siteName The site name
226     * @param lang The content language
227     * @param zoneItem The zone item holding the service
228     * @throws SAXException if an error occurs
229     */
230    protected void saxAdditionalInformation(String siteName, String lang, ZoneItem zoneItem) throws SAXException
231    {
232        // Nothing
233    }
234    
235    /**
236     * SAX the general term of use content if needed
237     * @param zoneItem The current zone item
238     * @throws MalformedURLException if an error occurs
239     * @throws SAXException if an error occurs
240     * @throws IOException if an error occurs
241     */
242    protected void saxTOSIfNeeded(ZoneItem zoneItem) throws MalformedURLException, SAXException, IOException
243    {
244        String tosMode = zoneItem.getServiceParameters().getValue("terms-of-service-mode");
245        if ("CONTENT".equals(tosMode))
246        {
247            String contentId = zoneItem.getServiceParameters().getValue("terms-of-service-content");
248            saxContent(contentId, "tos");
249        }
250    }
251    
252    /**
253     * SAX the success content if needed
254     * @param zoneItem The current zone item
255     * @throws MalformedURLException if an error occurs
256     * @throws SAXException if an error occurs
257     * @throws IOException if an error occurs
258     */
259    protected void saxSuccessContentIfNeeded(ZoneItem zoneItem) throws MalformedURLException, SAXException, IOException
260    {
261        String status = parameters.getParameter("status", null);
262        String step = parameters.getParameter("step", null);
263        if ("signup".equals(step) && "success".equals(status))
264        {
265            String successMode = zoneItem.getServiceParameters().getValue("success-mode");
266            if ("CONTENT".equals(successMode))
267            {
268                String contentId = zoneItem.getServiceParameters().getValue("success-content");
269                saxContent(contentId, "success");
270            }
271        }
272    }
273    
274    private void _addNotEmptyAttribute (AttributesImpl attrs, String name, String value)
275    {
276        if (StringUtils.isNotEmpty(value))
277        {
278            attrs.addCDATAAttribute(name, value);
279        }
280    }
281    
282    /**
283     * SAX the content of GTU
284     * @param contentId the id of GTU content
285     * @param rootTag The root XML tag
286     * @throws SAXException if an error occurs
287     * @throws IOException if failed to sax TGU content
288     * @throws MalformedURLException if failed to sax TGU content
289     */
290    protected void saxContent(String contentId, String rootTag) throws SAXException, MalformedURLException, IOException
291    {
292        try
293        {
294            Content gtu = _resolver.resolveById(contentId);
295                    
296            XMLUtils.startElement(contentHandler, rootTag);
297            
298            AttributesImpl attrs = new AttributesImpl();
299            attrs.addCDATAAttribute("id", gtu.getId());
300            attrs.addCDATAAttribute("name", gtu.getName());
301            attrs.addCDATAAttribute("title", gtu.getTitle(null));
302            attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(gtu.getLastModified()));
303            
304            XMLUtils.startElement(contentHandler, "content", attrs);
305            
306            String uri = "cocoon://_content.html?contentId=" + gtu.getId() + "&metadataSetName=main";
307            SitemapSource src = null;
308            
309            try
310            {
311                src = (SitemapSource) _srcResolver.resolveURI(uri);
312                src.toSAX(new IgnoreRootHandler(contentHandler));
313            }
314            finally
315            {
316                _srcResolver.release(src);
317            }
318            
319            XMLUtils.endElement(contentHandler, "content");
320            
321            XMLUtils.endElement(contentHandler, rootTag);
322        }
323        catch (UnknownAmetysObjectException e)
324        {
325            getLogger().error("The GTU content with id '" + contentId + "' does not exist anymore");
326        }
327        
328    }
329    
330    /**
331     * Generate errors.
332     * @param errors the error list.
333     * @throws SAXException if an error occurs.
334     */
335    protected void saxErrors(Multimap<String, I18nizableText> errors) throws SAXException
336    {
337        XMLUtils.startElement(contentHandler, "errors");
338        
339        if (errors.containsKey("global"))
340        {
341            Collection<I18nizableText> globalErrors = errors.get("global");
342            
343            XMLUtils.startElement(contentHandler, "global");
344            
345            for (I18nizableText error : globalErrors)
346            {
347                AttributesImpl errorAttrs = new AttributesImpl();
348                if (!error.isI18n())
349                {
350                    errorAttrs.addCDATAAttribute("type", error.getLabel());
351                }
352                XMLUtils.createElement(contentHandler, "error", errorAttrs);
353            }
354            
355            XMLUtils.endElement(contentHandler, "global");
356        }
357        
358        for (String field : errors.keySet())
359        {
360            if (!field.equals("global"))
361            {
362                Collection<I18nizableText> fieldErrors = errors.get(field);
363                
364                AttributesImpl attrs = new AttributesImpl();
365                attrs.addCDATAAttribute("name", field);
366                
367                XMLUtils.startElement(contentHandler, "field", attrs);
368                
369                for (I18nizableText error : fieldErrors)
370                {
371                    error.toSAX(contentHandler, "error");
372                }
373                
374                XMLUtils.endElement(contentHandler, "field");
375            }
376        }
377        
378        XMLUtils.endElement(contentHandler, "errors");
379    }
380    
381    /**
382     * Generate errors which could prevent the service from working.
383     * @param publicSignup indicate if public signup is enabled for the site.
384     * @param signupPage the signup page.
385     * @param pwdChangePage the lost password page. Can be null.
386     * @param tosPage the page of terms of use. Can be null.
387     * @param successPage the page of success. Can be null.
388     * @param tosContent the content for terms of use. Can be null.
389     * @param successContent the content for success. Can be null.
390     * @throws SAXException if an error occurs.
391     */
392    protected void saxWarnings(Boolean publicSignup, Page signupPage, Page pwdChangePage, Page tosPage, Page successPage, Content tosContent, Content successContent) throws SAXException
393    {
394        
395        XMLUtils.startElement(contentHandler, "warnings");
396        
397        if (publicSignup == null || !publicSignup)
398        {
399            XMLUtils.startElement(contentHandler, "warning");
400            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_NO_PUBLIC_SIGNUP").toSAX(contentHandler);
401            XMLUtils.endElement(contentHandler, "warning");
402        }
403        
404        if (signupPage == null)
405        {
406            XMLUtils.startElement(contentHandler, "warning");
407            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_NO_SIGNUP_PAGE").toSAX(contentHandler);
408            XMLUtils.endElement(contentHandler, "warning");
409        }
410        else if (!_isValid(signupPage))
411        {
412            XMLUtils.startElement(contentHandler, "warning");
413            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_SIGNUP_PAGE").toSAX(contentHandler);
414            XMLUtils.endElement(contentHandler, "warning");
415        }
416        
417        if (pwdChangePage == null)
418        {
419            XMLUtils.startElement(contentHandler, "warning");
420            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_NO_PASSWORD_CHANGE_PAGE").toSAX(contentHandler);
421            XMLUtils.endElement(contentHandler, "warning");
422        }
423        else if (!_isValid(pwdChangePage))
424        {
425            XMLUtils.startElement(contentHandler, "warning");
426            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_PASSWORD_CHANGE_PAGE").toSAX(contentHandler);
427            XMLUtils.endElement(contentHandler, "warning");
428        }
429        
430        if (tosPage != null && !_isValid(tosPage))
431        {
432            XMLUtils.startElement(contentHandler, "warning");
433            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_TOS_PAGE").toSAX(contentHandler);
434            XMLUtils.endElement(contentHandler, "warning");
435        }
436        
437        if (successPage != null && !_isValid(successPage))
438        {
439            XMLUtils.startElement(contentHandler, "warning");
440            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_SUCCESS_PAGE").toSAX(contentHandler);
441            XMLUtils.endElement(contentHandler, "warning");
442        }
443        
444        if (tosContent != null && !_isValid(tosContent))
445        {
446            XMLUtils.startElement(contentHandler, "warning");
447            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_TOS_CONTENT").toSAX(contentHandler);
448            XMLUtils.endElement(contentHandler, "warning");
449        }
450        
451        if (successContent != null && !_isValid(successContent))
452        {
453            XMLUtils.startElement(contentHandler, "warning");
454            new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_SUCCESS_CONTENT").toSAX(contentHandler);
455            XMLUtils.endElement(contentHandler, "warning");
456        }
457        
458        XMLUtils.endElement(contentHandler, "warnings");
459    }
460    
461    private boolean _isValid(Page page)
462    {
463        Skin skin = _skinManager.getSkin(page.getSite().getSkinId());
464        
465        Session liveSession = null;
466        try
467        {
468            liveSession = _repository.login(WebConstants.LIVE_WORKSPACE);
469            
470            if (!_synchronizeComponent.isPageValid(page, skin) || !_synchronizeComponent.isHierarchyValid(page, liveSession))
471            {
472                return false;
473            }
474            return true;
475        }
476        catch (RepositoryException e)
477        {
478            throw new RuntimeException("Unable to check live workspace", e);
479        }
480        finally
481        {
482            if (liveSession != null)
483            {
484                liveSession.logout();
485            }
486        }
487    }
488    
489    private boolean _isValid(Content content)
490    {
491        if (content instanceof VersionableAmetysObject)
492        {
493            return Arrays.asList(((VersionableAmetysObject) content).getAllLabels()).contains(WebConstants.LIVE_LABEL);
494        }
495        
496        return false;
497    }
498}