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