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