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.lang3.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 default 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 lang = StringUtils.defaultIfBlank(user.getLanguage(), language); 445 String sender = user.getEmail(); 446 447 if (StringUtils.isEmpty(sender)) 448 { 449 getLogger().error("Cart's owner has no email, his ODF cart selection can not be shared"); 450 result.put("success", false); 451 result.put("error", "no-owner-mail"); 452 return result; 453 } 454 455 List<ODFCartItem> items = itemIds.stream() 456 .map(i -> getCartItem(i)) 457 .filter(Objects::nonNull) 458 .collect(Collectors.toList()); 459 460 Site site = _siteManager.getSite(siteName); 461 Map<String, I18nizableTextParameter> i18nparam = new HashMap<>(); 462 i18nparam.put("siteTitle", new I18nizableText(site.getTitle())); // {siteTitle} 463 464 I18nizableText i18nTextSubject = new I18nizableText("plugin.odf-web", "PLUGINS_ODFWEB_CART_SHARE_MAIL_SUBJECT", i18nparam); 465 String subject = _i18nUtils.translate(i18nTextSubject, lang); 466 467 String htmlBody = null; 468 String textBody = null; 469 try 470 { 471 472 htmlBody = getMailBody(items, message, owner, siteName, lang, false); 473 textBody = getMailBody(items, message, owner, siteName, lang, true); 474 } 475 catch (IOException e) 476 { 477 getLogger().error("Fail to get mail body to share ODF cart selection", e); 478 result.put("success", false); 479 return result; 480 } 481 482 List<String> mailsInError = new ArrayList<>(); 483 484 for (String recipient : recipients) 485 { 486 try 487 { 488 String prettyHTMLBody; 489 try 490 { 491 prettyHTMLBody = StandardMailBodyHelper.newHTMLBody() 492 .withLanguage(lang) 493 .withTitle(subject) 494 .withMessage(htmlBody.replaceAll("\n", "")) // remove breaklines 495 .withLink(site.getUrl(), new I18nizableText("plugin.odf-web", "PLUGINS_ODFWEB_CART_SHARE_MAIL_HTML_BODY_SITE_LINK")) 496 .build(); 497 } 498 catch (IOException e) 499 { 500 getLogger().warn("Failed to build wrapped HTML body for ODF cart. Fallback to no wrapped mail", e); 501 prettyHTMLBody = htmlBody; 502 } 503 504 505 SendMailHelper.newMail() 506 .withSubject(subject) 507 .withHTMLBody(prettyHTMLBody) 508 .withTextBody(textBody) 509 .withRecipient(recipient) 510 .withSender(sender) 511 .sendMail(); 512 } 513 catch (MessagingException | IOException e) 514 { 515 getLogger().error("Failed to send ODF cart selection to '" + recipient + "'", e); 516 mailsInError.add(recipient); 517 } 518 } 519 520 if (mailsInError.size() > 0) 521 { 522 result.put("success", false); 523 result.put("mailsInError", mailsInError); 524 } 525 else 526 { 527 result.put("success", true); 528 } 529 530 return result; 531 } 532 533 /** 534 * Get the mail subject for sharing cart 535 * @param siteName The site name 536 * @param language the language 537 * @return the mail subject 538 */ 539 protected String getMailSubject(String siteName, String language) 540 { 541 Site site = _siteManager.getSite(siteName); 542 Map<String, I18nizableTextParameter> i18nparam = new HashMap<>(); 543 i18nparam.put("siteTitle", new I18nizableText(site.getTitle())); // {siteTitle} 544 545 I18nizableText i18nTextSubject = new I18nizableText("plugin.odf-web", "PLUGINS_ODFWEB_CART_SHARE_MAIL_SUBJECT", i18nparam); 546 return _i18nUtils.translate(i18nTextSubject, language); 547 } 548 549 /** 550 * Get the mail body to sharing cart 551 * @param items The cart's items 552 * @param message The message 553 * @param owner The cart's owner 554 * @param siteName The site name 555 * @param language the language 556 * @param text true to get the body to text body (html otherwise) 557 * @return the cart items to HTML format 558 * @throws IOException if failed to mail body 559 */ 560 protected String getMailBody(List<ODFCartItem> items, String message, UserIdentity owner, String siteName, String language, boolean text) throws IOException 561 { 562 Request request = ContextHelper.getRequest(_context); 563 564 String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 565 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 566 567 Source source = null; 568 try 569 { 570 // Force live workspace and FRONT context to resolve page 571 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE); 572 _renderingContextHandler.setRenderingContext(RenderingContext.FRONT); 573 574 Map<String, Object> parameters = new HashMap<>(); 575 576 parameters.put("items", items); 577 parameters.put("message", message); 578 parameters.put("owner", owner); 579 parameters.put("siteName", siteName); 580 parameters.put("lang", language); 581 parameters.put("format", text ? "text" : "html"); 582 583 source = _srcResolver.resolveURI("cocoon://_plugins/odf-web/cart/mail/body", null, parameters); 584 585 try (InputStream is = source.getInputStream()) 586 { 587 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 588 SourceUtil.copy(is, bos); 589 590 return bos.toString("UTF-8"); 591 } 592 } 593 finally 594 { 595 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace); 596 _renderingContextHandler.setRenderingContext(currentContext); 597 598 if (source != null) 599 { 600 _srcResolver.release(source); 601 } 602 } 603 } 604 605 /** 606 * SAX the cart's items 607 * @param contentHandler The content handler to sax into 608 * @param owner the cart owner 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 * @throws UserPreferencesException if failed to get cart items 613 */ 614 public void saxCartItems(ContentHandler contentHandler, UserIdentity owner, String siteName) throws SAXException, IOException, UserPreferencesException 615 { 616 List<ODFCartItem> items = getCartItems(owner, siteName); 617 618 XMLUtils.startElement(contentHandler, "items"); 619 for (ODFCartItem item : items) 620 { 621 saxCartItem(contentHandler, item, siteName); 622 623 } 624 XMLUtils.endElement(contentHandler, "items"); 625 626 } 627 628 /** 629 * SAX a cart's item 630 * @param contentHandler The content handler to sax into 631 * @param item the cart's item 632 * @param siteName the site name 633 * @throws SAXException if an error occurred while saxing 634 * @throws IOException if an I/O exception occurred 635 */ 636 public void saxCartItem(ContentHandler contentHandler, ODFCartItem item, String siteName) throws SAXException, IOException 637 { 638 AttributesImpl attrs = new AttributesImpl(); 639 attrs.addCDATAAttribute("id", item.getId()); 640 XMLUtils.startElement(contentHandler, "item", attrs); 641 642 Content content = item.getContent(); 643 saxTypes(contentHandler, content.getTypes()); 644 saxContent(contentHandler, content, "cart"); 645 saxPage(contentHandler, item, siteName); 646 647 Program parentProgram = item.getParentProgram(); 648 if (parentProgram != null) 649 { 650 attrs = new AttributesImpl(); 651 attrs.addCDATAAttribute("id", parentProgram.getId()); 652 attrs.addCDATAAttribute("title", parentProgram.getTitle()); 653 Page parentPage = _odfPageResolver.getProgramPage(parentProgram, siteName); 654 if (parentPage != null) 655 { 656 attrs.addCDATAAttribute("pageId", parentPage.getId()); 657 } 658 XMLUtils.createElement(contentHandler, "parent", attrs); 659 660 } 661 XMLUtils.endElement(contentHandler, "item"); 662 } 663 664 /** 665 * Get the JSON representation of a cart item 666 * @param item The cart's item 667 * @param siteName The site name 668 * @param viewName The name of content view to use 669 * @return The cart items properties 670 * @throws IOException if failed to read content view 671 */ 672 public Map<String, Object> cartItem2Json(ODFCartItem item, String siteName, String viewName) throws IOException 673 { 674 Map<String, Object> result = new HashMap<>(); 675 676 Content content = item.getContent(); 677 678 result.put("id", item.getId()); 679 result.put("contentId", content.getId()); 680 result.put("title", content.getTitle()); 681 result.put("name", content.getName()); 682 683 Program parentProgram = item.getParentProgram(); 684 if (parentProgram != null) 685 { 686 result.put("parentProgramId", parentProgram.getId()); 687 result.put("parentProgramTitle", parentProgram.getTitle()); 688 } 689 690 Page page = getPage(item, siteName); 691 if (page != null) 692 { 693 result.put("pageId", page.getId()); 694 result.put("pageTitle", page.getTitle()); 695 result.put("pagePath", page.getPathInSitemap()); 696 } 697 698 String cTypeId = content.getTypes()[0]; 699 ContentType cType = _cTypeEP.getExtension(cTypeId); 700 701 result.put("contentTypeId", cTypeId); 702 result.put("contentTypeLabel", cType.getLabel()); 703 704 if (viewName != null && _cTypesHelper.getView(viewName, content.getTypes(), content.getMixinTypes()) != null) 705 { 706 String uri = _contentHelper.getContentHtmlViewUrl(content, viewName, parentProgram != null ? Map.of("parentProgramId", parentProgram.getId()) : Map.of()); 707 SitemapSource src = null; 708 709 try 710 { 711 src = (SitemapSource) _srcResolver.resolveURI(uri); 712 try (InputStream is = src.getInputStream()) 713 { 714 String view = IOUtils.toString(is, StandardCharsets.UTF_8); 715 result.put("view", view); 716 } 717 } 718 finally 719 { 720 _srcResolver.release(src); 721 } 722 } 723 724 if (content instanceof AbstractProgram) 725 { 726 try 727 { 728 boolean subscriber = _currentUserProvider.getUser() != null && isSubscriber(_currentUserProvider.getUser(), item.getId(), siteName); 729 result.put("subscriber", subscriber); 730 } 731 catch (UserPreferencesException e) 732 { 733 getLogger().error("Fail to check if current user subscribes to content {}. It supposes he is not.", content.getId(), e); 734 result.put("subscriber", false); 735 } 736 737 additionalItemInfo(item, (AbstractProgram) content, result); 738 } 739 else if (content instanceof Course) 740 { 741 additionalItemInfo(item, (Course) content, result); 742 } 743 744 return result; 745 } 746 747 /** 748 * Get the additional information for {@link AbstractProgram} cart item 749 * @param item the odf cart item 750 * @param abstractProgram the abstract program 751 * @param infos the information to be completed 752 */ 753 protected void additionalItemInfo(ODFCartItem item, AbstractProgram abstractProgram, Map<String, Object> infos) 754 { 755 // Nothing 756 } 757 758 /** 759 * Get the additional information for {@link Course} cart item 760 * @param item the odf cart item 761 * @param course the abstract program 762 * @param infos the information to be completed 763 */ 764 protected void additionalItemInfo(ODFCartItem item, Course course, Map<String, Object> infos) 765 { 766 double ects = course.getEcts(); 767 if (ects > 0D) 768 { 769 infos.put("ects", ects); 770 } 771 772 double numberOfHours = course.getNumberOfHours(); 773 if (numberOfHours > 0D) 774 { 775 infos.put("nbHours", numberOfHours); 776 } 777 778 List<CoursePart> courseParts = course.getCourseParts(); 779 if (!courseParts.isEmpty()) 780 { 781 List<Map<String, Object>> courseparts = new ArrayList<>(); 782 for (CoursePart coursePart : course.getCourseParts()) 783 { 784 Map<String, Object> coursepart = new HashMap<>(); 785 coursepart.put("nature", coursePart.getNature()); 786 coursepart.put("nbHours", coursePart.getNumberOfHours()); 787 } 788 789 infos.put("courseparts", courseparts); 790 } 791 } 792 793 /** 794 * Sax the content types 795 * @param handler The content handler to sax into 796 * @param types The content types 797 * @throws SAXException if an error occurred while saxing 798 */ 799 protected void saxTypes(ContentHandler handler, String[] types) throws SAXException 800 { 801 XMLUtils.startElement(handler, "types"); 802 803 for (String id : types) 804 { 805 ContentType cType = _cTypeEP.getExtension(id); 806 if (cType != null) 807 { 808 AttributesImpl attrs = new AttributesImpl(); 809 attrs.addCDATAAttribute("id", cType.getId()); 810 811 XMLUtils.startElement(handler, "type", attrs); 812 cType.getLabel().toSAX(handler); 813 XMLUtils.endElement(handler, "type"); 814 } 815 } 816 XMLUtils.endElement(handler, "types"); 817 } 818 819 /** 820 * SAX the content view 821 * @param handler The content handler to sax into 822 * @param content The content 823 * @param viewName The view name 824 * @throws SAXException if an error occurred while saxing 825 * @throws IOException if an I/O exception occurred 826 */ 827 protected void saxContent (ContentHandler handler, Content content, String viewName) throws SAXException, IOException 828 { 829 AttributesImpl attrs = new AttributesImpl(); 830 attrs.addCDATAAttribute("id", content.getId()); 831 attrs.addCDATAAttribute("name", content.getName()); 832 attrs.addCDATAAttribute("title", content.getTitle(null)); 833 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.zonedDateTimeToString(content.getLastModified())); 834 835 XMLUtils.startElement(handler, "content", attrs); 836 837 if (_cTypesHelper.getView("cart", content.getTypes(), content.getMixinTypes()) != null) 838 { 839 String uri = _contentHelper.getContentHtmlViewUrl(content, viewName); 840 SitemapSource src = null; 841 842 try 843 { 844 src = (SitemapSource) _srcResolver.resolveURI(uri); 845 src.toSAX(new IgnoreRootHandler(handler)); 846 } 847 finally 848 { 849 _srcResolver.release(src); 850 } 851 } 852 853 XMLUtils.endElement(handler, "content"); 854 } 855 856 /** 857 * Sax the content's page 858 * @param handler The content handler to sax into 859 * @param item The cart's item 860 * @param siteName The current site name 861 * @throws SAXException if an error occurred while saxing 862 */ 863 protected void saxPage(ContentHandler handler, ODFCartItem item, String siteName) throws SAXException 864 { 865 Page page = getPage(item, siteName); 866 if (page != null) 867 { 868 String pageId = page.getId(); 869 870 AttributesImpl attrs = new AttributesImpl(); 871 attrs.addCDATAAttribute("id", pageId); 872 attrs.addCDATAAttribute("path", ResolveURIComponent.resolve("page", pageId)); 873 XMLUtils.createElement(handler, "page", attrs, page.getTitle()); 874 } 875 } 876 877 /** 878 * Get the page associated to this cart's item 879 * @param item The item 880 * @param siteName The site name 881 * @return the page or <code>null</code> if not found 882 */ 883 protected Page getPage(ODFCartItem item, String siteName) 884 { 885 Content content = item.getContent(); 886 if (content instanceof Course) 887 { 888 return _odfPageResolver.getCoursePage((Course) content, (AbstractProgram) item.getParentProgram(), siteName); 889 } 890 else if (content instanceof Program) 891 { 892 return _odfPageResolver.getProgramPage((Program) content, siteName); 893 } 894 else if (content instanceof SubProgram) 895 { 896 return _odfPageResolver.getSubProgramPage((SubProgram) content, item.getParentProgram(), siteName); 897 } 898 899 getLogger().info("No page found of content {} in ODF cart", content.getId()); 900 return null; 901 } 902 903 class ODFCartItem 904 { 905 private Content _content; 906 private Program _parentProgram; 907 908 public ODFCartItem(Content content) 909 { 910 this(content, null); 911 } 912 913 public ODFCartItem(Content content, Program parentProgram) 914 { 915 _content = content; 916 _parentProgram = parentProgram; 917 } 918 919 String getId() 920 { 921 return _content.getId() + (_parentProgram != null ? ";" + _parentProgram.getId() : ""); 922 } 923 924 Content getContent() 925 { 926 return _content; 927 } 928 929 Program getParentProgram() 930 { 931 return _parentProgram; 932 } 933 934 @Override 935 public int hashCode() 936 { 937 return Objects.hash(getId()); 938 } 939 940 @Override 941 public boolean equals(Object other) 942 { 943 return other != null && getClass() == other.getClass() && getId().equals(((ODFCartItem) other).getId()); 944 } 945 } 946}