001/* 002 * Copyright 2019 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.odfweb.cart; 017 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.nio.charset.StandardCharsets; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Objects; 029import java.util.stream.Collectors; 030 031import javax.mail.MessagingException; 032 033import org.apache.avalon.framework.component.Component; 034import org.apache.avalon.framework.context.Context; 035import org.apache.avalon.framework.context.ContextException; 036import org.apache.avalon.framework.context.Contextualizable; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.cocoon.components.ContextHelper; 041import org.apache.cocoon.components.source.impl.SitemapSource; 042import org.apache.cocoon.environment.Request; 043import org.apache.cocoon.xml.AttributesImpl; 044import org.apache.cocoon.xml.XMLUtils; 045import org.apache.commons.io.IOUtils; 046import org.apache.commons.lang.StringUtils; 047import org.apache.excalibur.source.Source; 048import org.apache.excalibur.source.SourceResolver; 049import org.apache.excalibur.source.SourceUtil; 050import org.xml.sax.ContentHandler; 051import org.xml.sax.SAXException; 052 053import org.ametys.cms.contenttype.ContentType; 054import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 055import org.ametys.cms.contenttype.ContentTypesHelper; 056import org.ametys.cms.repository.Content; 057import org.ametys.cms.transformation.xslt.ResolveURIComponent; 058import org.ametys.core.user.CurrentUserProvider; 059import org.ametys.core.user.User; 060import org.ametys.core.user.UserIdentity; 061import org.ametys.core.userpref.UserPreferencesException; 062import org.ametys.core.userpref.UserPreferencesManager; 063import org.ametys.core.util.DateUtils; 064import org.ametys.core.util.I18nUtils; 065import org.ametys.core.util.IgnoreRootHandler; 066import org.ametys.core.util.mail.SendMailHelper; 067import org.ametys.odf.course.Course; 068import org.ametys.odf.coursepart.CoursePart; 069import org.ametys.odf.program.AbstractProgram; 070import org.ametys.odf.program.Program; 071import org.ametys.odf.program.SubProgram; 072import org.ametys.plugins.core.user.UserHelper; 073import org.ametys.plugins.odfweb.repository.OdfPageResolver; 074import org.ametys.plugins.repository.AmetysObjectResolver; 075import org.ametys.plugins.repository.UnknownAmetysObjectException; 076import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 077import org.ametys.runtime.i18n.I18nizableText; 078import org.ametys.runtime.plugin.component.AbstractLogEnabled; 079import org.ametys.web.WebConstants; 080import org.ametys.web.renderingcontext.RenderingContext; 081import org.ametys.web.renderingcontext.RenderingContextHandler; 082import org.ametys.web.repository.page.Page; 083import org.ametys.web.repository.site.Site; 084import org.ametys.web.repository.site.SiteManager; 085import org.ametys.web.userpref.FOUserPreferencesConstants; 086 087/** 088 * Component to handle ODF cart items 089 * 090 */ 091public class ODFCartManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 092{ 093 /** The avalon role */ 094 public static final String ROLE = ODFCartManager.class.getName(); 095 096 /** The id of user preference for cart's elements */ 097 public static final String CART_USER_PREF_CONTENT_IDS = "cartOdfContentIds"; 098 099 /** The id of user preference for subscription */ 100 public static final String SUBSCRIPTION_USER_PREF_CONTENT_IDS = "subscriptionOdfContentIds"; 101 102 private UserPreferencesManager _userPrefManager; 103 private AmetysObjectResolver _resolver; 104 private SourceResolver _srcResolver; 105 private OdfPageResolver _odfPageResolver; 106 private ContentTypeExtensionPoint _cTypeEP; 107 private ContentTypesHelper _cTypesHelper; 108 private ODFCartUserPreferencesStorage _odfUserPrefStorage; 109 private I18nUtils _i18nUtils; 110 private UserHelper _userHelper; 111 private SiteManager _siteManager; 112 private RenderingContextHandler _renderingContextHandler; 113 private CurrentUserProvider _currentUserProvider; 114 115 private Context _context; 116 117 118 119 120 @Override 121 public void service(ServiceManager serviceManager) throws ServiceException 122 { 123 _userPrefManager = (UserPreferencesManager) serviceManager.lookup(UserPreferencesManager.ROLE + ".FO"); 124 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 125 _srcResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 126 _odfPageResolver = (OdfPageResolver) serviceManager.lookup(OdfPageResolver.ROLE); 127 _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 128 _cTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 129 _odfUserPrefStorage = (ODFCartUserPreferencesStorage) serviceManager.lookup(ODFCartUserPreferencesStorage.ROLE); 130 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 131 _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE); 132 _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE); 133 _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); 134 _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 135 } 136 137 public void contextualize(Context context) throws ContextException 138 { 139 _context = context; 140 } 141 142 /** 143 * Get the id of ODF's cart items for a given user 144 * @param user the user 145 * @param siteName the current site name 146 * @return the list of contents' id 147 * @throws UserPreferencesException if failed to get cart items 148 */ 149 public List<String> getCartItemIds(UserIdentity user, String siteName) throws UserPreferencesException 150 { 151 return getCartItemIds(user, siteName, CART_USER_PREF_CONTENT_IDS); 152 } 153 154 /** 155 * Get the id of ODF's cart items for which a given user is a subscriber 156 * @param user the user 157 * @param siteName the current site name 158 * @return the list of contents' id 159 * @throws UserPreferencesException if failed to get cart items 160 */ 161 public List<String> getItemIdsWithSubscription(UserIdentity user, String siteName) throws UserPreferencesException 162 { 163 return getCartItemIds(user, siteName, SUBSCRIPTION_USER_PREF_CONTENT_IDS); 164 } 165 166 /** 167 * Get the id of items for a given user 168 * @param user the user 169 * @param siteName the current site name 170 * @param userPrefsId The id of user preferences 171 * @return the list of contents' id 172 * @throws UserPreferencesException if failed to get cart items 173 */ 174 protected List<String> getCartItemIds(UserIdentity user, String siteName, String userPrefsId) throws UserPreferencesException 175 { 176 Map<String, String> contextVars = new HashMap<>(); 177 contextVars.put(FOUserPreferencesConstants.CONTEXT_VAR_SITENAME, siteName); 178 179 String contentIdsAsStr = _userPrefManager.getUserPreferenceAsString(user, "/sites/" + siteName, contextVars, userPrefsId); 180 if (StringUtils.isNotBlank(contentIdsAsStr)) 181 { 182 return Arrays.asList(StringUtils.split(contentIdsAsStr, ",")); 183 } 184 185 return Collections.emptyList(); 186 } 187 188 /** 189 * Get the ODF's cart items for a given user 190 * @param owner the user 191 * @param siteName the current site name 192 * @return the list of contents 193 * @throws UserPreferencesException if failed to get cart items 194 */ 195 public List<ODFCartItem> getCartItems(UserIdentity owner, String siteName) throws UserPreferencesException 196 { 197 List<ODFCartItem> items = new ArrayList<>(); 198 199 List<String> itemIds = getCartItemIds(owner, siteName); 200 for (String itemId : itemIds) 201 { 202 ODFCartItem item = getCartItem(itemId); 203 if (item != null) 204 { 205 items.add(item); 206 } 207 else 208 { 209 getLogger().warn("The item with id '{}' stored in cart of user {} does not match an existing content anymore. It will be ignored", itemId, owner); 210 } 211 } 212 213 return items; 214 } 215 216 /** 217 * Get the ODF's cart items for which the given user is a subscriber 218 * @param owner the user 219 * @param siteName the current site name 220 * @return the list of contents 221 * @throws UserPreferencesException if failed to get subscriptions 222 */ 223 public List<ODFCartItem> getItemsWithSubscription(UserIdentity owner, String siteName) throws UserPreferencesException 224 { 225 List<ODFCartItem> items = new ArrayList<>(); 226 227 List<String> itemIds = getItemIdsWithSubscription(owner, siteName); 228 for (String itemId : itemIds) 229 { 230 ODFCartItem item = getCartItem(itemId); 231 if (item != null) 232 { 233 items.add(item); 234 } 235 else 236 { 237 getLogger().warn("The item with id '{}' stored in subscription of user {} does not match an existing content anymore. It will be ignored", itemId, owner); 238 } 239 } 240 241 return items; 242 } 243 244 /** 245 * Get a cart item from its id 246 * @param itemId the item's id 247 * @return the cart item or null if no content was found 248 */ 249 public ODFCartItem getCartItem(String itemId) 250 { 251 int i = itemId.indexOf(';'); 252 253 String contentId = itemId; 254 String parentId = null; 255 256 if (i != -1) 257 { 258 contentId = itemId.substring(0, i); 259 parentId = itemId.substring(i + 1); 260 } 261 262 try 263 { 264 return new ODFCartItem(_resolver.resolveById(contentId), parentId != null ? _resolver.resolveById(parentId) : null); 265 } 266 catch (UnknownAmetysObjectException e) 267 { 268 return null; 269 } 270 } 271 272 /** 273 * Set the cart's items 274 * @param owner The cart owner 275 * @param itemIds the id of items to set in the cart 276 * @param siteName the site name 277 * @throws UserPreferencesException if failed to save cart 278 */ 279 public void setCartItems(UserIdentity owner, List<String> itemIds, String siteName) throws UserPreferencesException 280 { 281 saveItemsInUserPreference(owner, itemIds, siteName, CART_USER_PREF_CONTENT_IDS); 282 } 283 284 285 /** 286 * Subscribe to a list of items. All subscriptions are replaced by the given items. 287 * @param owner The cart owner 288 * @param itemIds the id of items to subscribe to. 289 * @param siteName the site name 290 * @throws UserPreferencesException if failed to save subscriptions 291 */ 292 public void setSubscription(UserIdentity owner, List<String> itemIds, String siteName) throws UserPreferencesException 293 { 294 saveItemsInUserPreference(owner, itemIds, siteName, SUBSCRIPTION_USER_PREF_CONTENT_IDS); 295 } 296 297 /** 298 * Save the given items into the given user preference 299 * @param owner the user 300 * @param itemIds the id of items 301 * @param siteName the site name 302 * @param userPrefsId the id of user preference 303 * @throws UserPreferencesException if failed to save user preference 304 */ 305 public void saveItemsInUserPreference(UserIdentity owner, List<String> itemIds, String siteName, String userPrefsId) throws UserPreferencesException 306 { 307 Map<String, String> contextVars = new HashMap<>(); 308 contextVars.put(FOUserPreferencesConstants.CONTEXT_VAR_SITENAME, siteName); 309 310 Map<String, String> preferences = new HashMap<>(); 311 preferences.put(userPrefsId, StringUtils.join(itemIds, ",")); 312 313 _odfUserPrefStorage.setUserPreferences(owner, "/sites/" + siteName, contextVars, preferences); 314 } 315 316 /** 317 * Add a content to the cart 318 * @param owner the cart owner 319 * @param itemId the id of content to add into the cart 320 * @param siteName the site name 321 * @return true if the content was successfully added 322 * @throws UserPreferencesException if failed to save cart 323 */ 324 public boolean addCartItem(UserIdentity owner, String itemId, String siteName) throws UserPreferencesException 325 { 326 List<ODFCartItem> items = getCartItems(owner, siteName); 327 328 ODFCartItem item = getCartItem(itemId); 329 if (item != null) 330 { 331 items.add(item); 332 } 333 else 334 { 335 getLogger().warn("Unknown item with id {}. It cannot be added to user cart", itemId); 336 return false; 337 } 338 339 List<String> itemIds = items.stream() 340 .map(c -> c.getId()) 341 .collect(Collectors.toList()); 342 343 setCartItems(owner, itemIds, siteName); 344 345 return true; 346 } 347 348 /** 349 * determines if the user subscribes to this item 350 * @param owner the user 351 * @param itemId the id of content 352 * @param siteName the site name 353 * @return true if the user subscribes to this item, false otherwise 354 * @throws UserPreferencesException if failed to check subscription 355 */ 356 public boolean isSubscriber(UserIdentity owner, String itemId, String siteName) throws UserPreferencesException 357 { 358 List<String> subscriptions = getItemIdsWithSubscription(owner, siteName); 359 return subscriptions.contains(itemId); 360 } 361 362 /** 363 * Subscribe to a content 364 * @param owner the cart owner 365 * @param itemId the id of content 366 * @param siteName the site name 367 * @return if the content was successfuly added to subscriptions 368 * @throws UserPreferencesException if failed to subscribe to content 369 */ 370 public boolean subscribe(UserIdentity owner, String itemId, String siteName) throws UserPreferencesException 371 { 372 List<ODFCartItem> items = getItemsWithSubscription(owner, siteName); 373 374 ODFCartItem item = getCartItem(itemId); 375 if (item != null) 376 { 377 items.add(item); 378 } 379 else 380 { 381 getLogger().warn("Unknown item with id {}. It cannot be added to user subscriptions", itemId); 382 return false; 383 } 384 385 List<String> itemIds = items.stream() 386 .map(c -> c.getId()) 387 .collect(Collectors.toList()); 388 389 setSubscription(owner, itemIds, siteName); 390 391 return true; 392 } 393 394 /** 395 * Unsubscribe to a content 396 * @param owner the cart owner 397 * @param itemId the id of content 398 * @param siteName the site name 399 * @return if the content was successfuly added to subscriptions 400 * @throws UserPreferencesException if failed to subscribe to content 401 */ 402 public boolean unsubscribe(UserIdentity owner, String itemId, String siteName) throws UserPreferencesException 403 { 404 List<ODFCartItem> items = getItemsWithSubscription(owner, siteName); 405 406 ODFCartItem item = getCartItem(itemId); 407 if (item != null) 408 { 409 items.remove(item); 410 } 411 else 412 { 413 getLogger().warn("Unknown item with id {}. It cannot be remove from user subscriptions", itemId); 414 return false; 415 } 416 417 List<String> itemIds = items.stream() 418 .map(c -> c.getId()) 419 .collect(Collectors.toList()); 420 421 setSubscription(owner, itemIds, siteName); 422 423 return true; 424 } 425 426 /** 427 * Share the cart's items by mail 428 * @param owner The cart owner 429 * @param itemIds the id of contents to set in the cart 430 * @param recipients the mails to share with 431 * @param siteName the site name 432 * @param language the language 433 * @param message the message to add to selection 434 * @return the results 435 */ 436 public Map<String, Object> shareCartItems(UserIdentity owner, List<String> itemIds, List<String> recipients, String siteName, String language, String message) 437 { 438 Map<String, Object> result = new HashMap<>(); 439 440 User user = _userHelper.getUser(owner); 441 String sender = user.getEmail(); 442 443 if (StringUtils.isEmpty(sender)) 444 { 445 getLogger().error("Cart's owner has no email, his ODF cart selection can not be shared"); 446 result.put("success", false); 447 result.put("error", "no-owner-mail"); 448 return result; 449 } 450 451 List<ODFCartItem> items = itemIds.stream() 452 .map(i -> getCartItem(i)) 453 .filter(Objects::nonNull) 454 .collect(Collectors.toList()); 455 456 Site site = _siteManager.getSite(siteName); 457 Map<String, I18nizableText> i18nparam = new HashMap<>(); 458 i18nparam.put("siteTitle", new I18nizableText(site.getTitle())); // {siteTitle} 459 460 I18nizableText i18nTextSubject = new I18nizableText("plugin.odf-web", "PLUGINS_ODFWEB_CART_SHARE_MAIL_SUBJECT", i18nparam); 461 String subject = _i18nUtils.translate(i18nTextSubject); 462 463 String htmlBody = null; 464 String textBody = null; 465 try 466 { 467 468 htmlBody = getMailBody(items, message, owner, siteName, language, false); 469 textBody = getMailBody(items, message, owner, siteName, language, true); 470 } 471 catch (IOException e) 472 { 473 getLogger().error("Fail to get mail body to share ODF cart selection", e); 474 result.put("success", false); 475 return result; 476 } 477 478 List<String> mailsInError = new ArrayList<>(); 479 480 for (String recipient : recipients) 481 { 482 try 483 { 484 SendMailHelper.sendMail(subject, htmlBody, textBody, Collections.singletonList(recipient), sender); 485 } 486 catch (MessagingException e) 487 { 488 getLogger().error("Failed to send ODF cart selection to '" + recipient + "'", e); 489 mailsInError.add(recipient); 490 } 491 } 492 493 if (mailsInError.size() > 0) 494 { 495 result.put("success", false); 496 result.put("mailsInError", mailsInError); 497 } 498 else 499 { 500 result.put("success", true); 501 } 502 503 return result; 504 } 505 506 /** 507 * Get the mail subject for sharing cart 508 * @param siteName The site name 509 * @param language the language 510 * @return the mail subject 511 */ 512 protected String getMailSubject(String siteName, String language) 513 { 514 Site site = _siteManager.getSite(siteName); 515 Map<String, I18nizableText> i18nparam = new HashMap<>(); 516 i18nparam.put("siteTitle", new I18nizableText(site.getTitle())); // {siteTitle} 517 518 I18nizableText i18nTextSubject = new I18nizableText("plugin.odf-web", "PLUGINS_ODFWEB_CART_SHARE_MAIL_SUBJECT", i18nparam); 519 return _i18nUtils.translate(i18nTextSubject, language); 520 } 521 522 /** 523 * Get the mail body to sharing cart 524 * @param items The cart's items 525 * @param message The message 526 * @param owner The cart's owner 527 * @param siteName The site name 528 * @param language the language 529 * @param text true to get the body to text body (html otherwise) 530 * @return the cart items to HTML format 531 * @throws IOException if failed to mail body 532 */ 533 protected String getMailBody(List<ODFCartItem> items, String message, UserIdentity owner, String siteName, String language, boolean text) throws IOException 534 { 535 Request request = ContextHelper.getRequest(_context); 536 537 String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 538 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 539 540 Source source = null; 541 try 542 { 543 // Force live workspace and FRONT context to resolve page 544 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE); 545 _renderingContextHandler.setRenderingContext(RenderingContext.FRONT); 546 547 Map<String, Object> parameters = new HashMap<>(); 548 549 parameters.put("items", items); 550 parameters.put("message", message); 551 parameters.put("owner", owner); 552 parameters.put("siteName", siteName); 553 parameters.put("lang", language); 554 parameters.put("format", text ? "text" : "html"); 555 556 source = _srcResolver.resolveURI("cocoon://_plugins/odf-web/cart/mail/body", null, parameters); 557 558 try (InputStream is = source.getInputStream()) 559 { 560 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 561 SourceUtil.copy(is, bos); 562 563 return bos.toString("UTF-8"); 564 } 565 } 566 finally 567 { 568 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace); 569 _renderingContextHandler.setRenderingContext(currentContext); 570 571 if (source != null) 572 { 573 _srcResolver.release(source); 574 } 575 } 576 } 577 578 /** 579 * SAX the cart's items 580 * @param contentHandler The content handler to sax into 581 * @param owner the cart owner 582 * @param siteName the site name 583 * @throws SAXException if an error occurred while saxing 584 * @throws IOException if an I/O exception occurred 585 * @throws UserPreferencesException if failed to get cart items 586 */ 587 public void saxCartItems(ContentHandler contentHandler, UserIdentity owner, String siteName) throws SAXException, IOException, UserPreferencesException 588 { 589 List<ODFCartItem> items = getCartItems(owner, siteName); 590 591 XMLUtils.startElement(contentHandler, "items"); 592 for (ODFCartItem item : items) 593 { 594 saxCartItem(contentHandler, item, siteName); 595 596 } 597 XMLUtils.endElement(contentHandler, "items"); 598 599 } 600 601 /** 602 * SAX a cart's item 603 * @param contentHandler The content handler to sax into 604 * @param item the cart's item 605 * @param siteName the site name 606 * @throws SAXException if an error occurred while saxing 607 * @throws IOException if an I/O exception occurred 608 */ 609 public void saxCartItem(ContentHandler contentHandler, ODFCartItem item, String siteName) throws SAXException, IOException 610 { 611 AttributesImpl attrs = new AttributesImpl(); 612 attrs.addCDATAAttribute("id", item.getId()); 613 XMLUtils.startElement(contentHandler, "item", attrs); 614 615 Content content = item.getContent(); 616 saxTypes(contentHandler, content.getTypes()); 617 saxContent(contentHandler, content, "cart"); 618 saxPage(contentHandler, item, siteName); 619 620 Program parentProgram = item.getParentProgram(); 621 if (parentProgram != null) 622 { 623 attrs = new AttributesImpl(); 624 attrs.addCDATAAttribute("id", parentProgram.getId()); 625 attrs.addCDATAAttribute("title", parentProgram.getTitle()); 626 Page parentPage = _odfPageResolver.getProgramPage(parentProgram, siteName); 627 if (parentPage != null) 628 { 629 attrs.addCDATAAttribute("pageId", parentPage.getId()); 630 } 631 XMLUtils.createElement(contentHandler, "parent", attrs); 632 633 } 634 XMLUtils.endElement(contentHandler, "item"); 635 } 636 637 /** 638 * Get the JSON representation of a cart item 639 * @param item The cart's item 640 * @param siteName The site name 641 * @param viewName The name of content view to use 642 * @return The cart items properties 643 * @throws IOException if failed to read content view 644 */ 645 public Map<String, Object> cartItem2Json(ODFCartItem item, String siteName, String viewName) throws IOException 646 { 647 Map<String, Object> result = new HashMap<>(); 648 649 Content content = item.getContent(); 650 651 result.put("id", item.getId()); 652 result.put("contentId", content.getId()); 653 result.put("title", content.getTitle()); 654 result.put("name", content.getName()); 655 656 Program parentProgram = item.getParentProgram(); 657 if (parentProgram != null) 658 { 659 result.put("parentProgramId", parentProgram.getId()); 660 result.put("parentProgramTitle", parentProgram.getTitle()); 661 } 662 663 Page page = getPage(item, siteName); 664 if (page != null) 665 { 666 result.put("pageId", page.getId()); 667 result.put("pageTitle", page.getTitle()); 668 result.put("pagePath", page.getPathInSitemap()); 669 } 670 671 String cTypeId = content.getTypes()[0]; 672 ContentType cType = _cTypeEP.getExtension(cTypeId); 673 674 result.put("contentTypeId", cTypeId); 675 result.put("contentTypeLabel", cType.getLabel()); 676 677 if (viewName != null && _cTypesHelper.getView(viewName, content.getTypes(), content.getMixinTypes()) != null) 678 { 679 String uri = "cocoon://_content.html?contentId=" + content.getId() + "&metadataSetName=" + viewName + (parentProgram != null ? "&parentProgramId=" + parentProgram.getId() : ""); 680 SitemapSource src = null; 681 682 try 683 { 684 src = (SitemapSource) _srcResolver.resolveURI(uri); 685 try (InputStream is = src.getInputStream()) 686 { 687 String view = IOUtils.toString(is, StandardCharsets.UTF_8); 688 result.put("view", view); 689 } 690 } 691 finally 692 { 693 _srcResolver.release(src); 694 } 695 } 696 697 if (content instanceof AbstractProgram) 698 { 699 try 700 { 701 boolean subscriber = _currentUserProvider.getUser() != null && isSubscriber(_currentUserProvider.getUser(), item.getId(), siteName); 702 result.put("subscriber", subscriber); 703 } 704 catch (UserPreferencesException e) 705 { 706 getLogger().error("Fail to check if current user subscribes to content {}. It supposes he is not.", content.getId(), e); 707 result.put("subscriber", false); 708 } 709 710 additionalItemInfo(item, (AbstractProgram) content, result); 711 } 712 else if (content instanceof Course) 713 { 714 additionalItemInfo(item, (Course) content, result); 715 } 716 717 return result; 718 } 719 720 /** 721 * Get the additional information for {@link AbstractProgram} cart item 722 * @param item the odf cart item 723 * @param abstractProgram the abstract program 724 * @param infos the information to be completed 725 */ 726 protected void additionalItemInfo(ODFCartItem item, AbstractProgram abstractProgram, Map<String, Object> infos) 727 { 728 // Nothing 729 } 730 731 /** 732 * Get the additional information for {@link Course} cart item 733 * @param item the odf cart item 734 * @param course the abstract program 735 * @param infos the information to be completed 736 */ 737 protected void additionalItemInfo(ODFCartItem item, Course course, Map<String, Object> infos) 738 { 739 double ects = course.getEcts(); 740 if (ects > 0D) 741 { 742 infos.put("ects", ects); 743 } 744 745 double numberOfHours = course.getNumberOfHours(); 746 if (numberOfHours > 0D) 747 { 748 infos.put("nbHours", numberOfHours); 749 } 750 751 List<CoursePart> courseParts = course.getCourseParts(); 752 if (!courseParts.isEmpty()) 753 { 754 List<Map<String, Object>> courseparts = new ArrayList<>(); 755 for (CoursePart coursePart : course.getCourseParts()) 756 { 757 Map<String, Object> coursepart = new HashMap<>(); 758 coursepart.put("nature", coursePart.getNature()); 759 coursepart.put("nbHours", coursePart.getNumberOfHours()); 760 } 761 762 infos.put("courseparts", courseparts); 763 } 764 } 765 766 /** 767 * Sax the content types 768 * @param handler The content handler to sax into 769 * @param types The content types 770 * @throws SAXException if an error occurred while saxing 771 */ 772 protected void saxTypes(ContentHandler handler, String[] types) throws SAXException 773 { 774 XMLUtils.startElement(handler, "types"); 775 776 for (String id : types) 777 { 778 ContentType cType = _cTypeEP.getExtension(id); 779 if (cType != null) 780 { 781 AttributesImpl attrs = new AttributesImpl(); 782 attrs.addCDATAAttribute("id", cType.getId()); 783 784 XMLUtils.startElement(handler, "type", attrs); 785 cType.getLabel().toSAX(handler); 786 XMLUtils.endElement(handler, "type"); 787 } 788 } 789 XMLUtils.endElement(handler, "types"); 790 } 791 792 /** 793 * SAX the content view 794 * @param handler The content handler to sax into 795 * @param content The content 796 * @param viewName The view name 797 * @throws SAXException if an error occurred while saxing 798 * @throws IOException if an I/O exception occurred 799 */ 800 protected void saxContent (ContentHandler handler, Content content, String viewName) throws SAXException, IOException 801 { 802 AttributesImpl attrs = new AttributesImpl(); 803 attrs.addCDATAAttribute("id", content.getId()); 804 attrs.addCDATAAttribute("name", content.getName()); 805 attrs.addCDATAAttribute("title", content.getTitle(null)); 806 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(content.getLastModified())); 807 808 XMLUtils.startElement(handler, "content", attrs); 809 810 if (_cTypesHelper.getView("cart", content.getTypes(), content.getMixinTypes()) != null) 811 { 812 String uri = "cocoon://_content.html?contentId=" + content.getId() + "&metadataSetName=" + viewName; 813 SitemapSource src = null; 814 815 try 816 { 817 src = (SitemapSource) _srcResolver.resolveURI(uri); 818 src.toSAX(new IgnoreRootHandler(handler)); 819 } 820 finally 821 { 822 _srcResolver.release(src); 823 } 824 } 825 826 XMLUtils.endElement(handler, "content"); 827 } 828 829 /** 830 * Sax the content's page 831 * @param handler The content handler to sax into 832 * @param item The cart's item 833 * @param siteName The current site name 834 * @throws SAXException if an error occurred while saxing 835 */ 836 protected void saxPage(ContentHandler handler, ODFCartItem item, String siteName) throws SAXException 837 { 838 Page page = getPage(item, siteName); 839 if (page != null) 840 { 841 String pageId = page.getId(); 842 843 AttributesImpl attrs = new AttributesImpl(); 844 attrs.addCDATAAttribute("id", pageId); 845 attrs.addCDATAAttribute("path", ResolveURIComponent.resolve("page", pageId)); 846 XMLUtils.createElement(handler, "page", attrs, page.getTitle()); 847 } 848 } 849 850 /** 851 * Get the page associated to this cart's item 852 * @param item The item 853 * @param siteName The site name 854 * @return the page or <code>null</code> if not found 855 */ 856 protected Page getPage(ODFCartItem item, String siteName) 857 { 858 Content content = item.getContent(); 859 if (content instanceof Course) 860 { 861 return _odfPageResolver.getCoursePage((Course) content, (AbstractProgram) item.getParentProgram(), siteName); 862 } 863 else if (content instanceof Program) 864 { 865 return _odfPageResolver.getProgramPage((Program) content, siteName); 866 } 867 else if (content instanceof SubProgram) 868 { 869 return _odfPageResolver.getSubProgramPage((SubProgram) content, item.getParentProgram(), siteName); 870 } 871 872 getLogger().info("No page found of content {} in ODF cart", content.getId()); 873 return null; 874 } 875 876 class ODFCartItem 877 { 878 private Content _content; 879 private Program _parentProgram; 880 881 public ODFCartItem(Content content) 882 { 883 this(content, null); 884 } 885 886 public ODFCartItem(Content content, Program parentProgram) 887 { 888 _content = content; 889 _parentProgram = parentProgram; 890 } 891 892 String getId() 893 { 894 return _content.getId() + (_parentProgram != null ? ";" + _parentProgram.getId() : ""); 895 } 896 897 Content getContent() 898 { 899 return _content; 900 } 901 902 Program getParentProgram() 903 { 904 return _parentProgram; 905 } 906 907 @Override 908 public int hashCode() 909 { 910 return Objects.hash(getId()); 911 } 912 913 @Override 914 public boolean equals(Object other) 915 { 916 return other != null && getClass() == other.getClass() && getId().equals(((ODFCartItem) other).getId()); 917 } 918 } 919}