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