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