001/* 002 * Copyright 2012 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 */ 016 017package org.ametys.cms.transformation.xslt; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Properties; 027 028import javax.xml.parsers.DocumentBuilder; 029import javax.xml.parsers.DocumentBuilderFactory; 030import javax.xml.transform.OutputKeys; 031import javax.xml.transform.Transformer; 032import javax.xml.transform.TransformerConfigurationException; 033import javax.xml.transform.TransformerException; 034import javax.xml.transform.TransformerFactory; 035import javax.xml.transform.URIResolver; 036import javax.xml.transform.dom.DOMResult; 037import javax.xml.transform.sax.SAXTransformerFactory; 038import javax.xml.transform.sax.TransformerHandler; 039import javax.xml.transform.stream.StreamSource; 040 041import org.apache.avalon.framework.context.Context; 042import org.apache.avalon.framework.context.ContextException; 043import org.apache.avalon.framework.logger.LogEnabled; 044import org.apache.avalon.framework.logger.Logger; 045import org.apache.avalon.framework.service.ServiceException; 046import org.apache.avalon.framework.service.ServiceManager; 047import org.apache.cocoon.components.ContextHelper; 048import org.apache.cocoon.components.source.SourceUtil; 049import org.apache.cocoon.environment.Request; 050import org.apache.cocoon.xml.XMLUtils; 051import org.apache.commons.lang.StringUtils; 052import org.apache.commons.lang3.ArrayUtils; 053import org.apache.excalibur.source.Source; 054import org.w3c.dom.Document; 055import org.w3c.dom.Element; 056import org.w3c.dom.Node; 057import org.w3c.dom.NodeList; 058import org.xml.sax.SAXException; 059 060import org.ametys.cms.content.ContentHelper; 061import org.ametys.cms.contenttype.ContentType; 062import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 063import org.ametys.cms.data.RichText; 064import org.ametys.cms.data.RichTextHelper; 065import org.ametys.cms.repository.Content; 066import org.ametys.cms.tag.CMSTag; 067import org.ametys.cms.tag.ColorableTag; 068import org.ametys.cms.tag.Tag; 069import org.ametys.cms.tag.TagProviderExtensionPoint; 070import org.ametys.cms.transformation.dom.TagElement; 071import org.ametys.core.util.dom.AmetysNodeList; 072import org.ametys.core.util.dom.EmptyElement; 073import org.ametys.core.util.dom.MapElement; 074import org.ametys.core.util.dom.StringElement; 075import org.ametys.plugins.explorer.resources.Resource; 076import org.ametys.plugins.explorer.resources.ResourceCollection; 077import org.ametys.plugins.explorer.resources.dom.ResourceCollectionElement; 078import org.ametys.plugins.repository.AmetysObjectResolver; 079import org.ametys.plugins.repository.AmetysRepositoryException; 080import org.ametys.plugins.repository.UnknownAmetysObjectException; 081import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 082import org.ametys.plugins.repository.metadata.CompositeMetadata; 083import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType; 084import org.ametys.plugins.repository.metadata.UnknownMetadataException; 085import org.ametys.plugins.repository.model.RepositoryDataContext; 086import org.ametys.plugins.repository.version.VersionAwareAmetysObject; 087import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 088import org.ametys.runtime.model.type.DataContext; 089 090/** 091 * Helper component to be used from XSL stylesheets. 092 */ 093public class AmetysXSLTHelper extends org.ametys.core.util.AmetysXSLTHelper implements LogEnabled 094{ 095 /** The Ametys object resolver */ 096 protected static AmetysObjectResolver _ametysObjectResolver; 097 /** The content types extension point */ 098 protected static ContentTypeExtensionPoint _cTypeExtensionPoint; 099 /** The tags provider */ 100 protected static TagProviderExtensionPoint _tagProviderExtPt; 101 /** Helper for content */ 102 protected static ContentHelper _contentHelper; 103 /** The avalon context */ 104 protected static Context _context; 105 /** The logger */ 106 protected static Logger _logger; 107 /** The sax parser */ 108 protected static RichTextHelper _richTextHelper; 109 110 @Override 111 public void contextualize(Context context) throws ContextException 112 { 113 super.contextualize(context); 114 _context = context; 115 } 116 117 @Override 118 public void enableLogging(Logger logger) 119 { 120 _logger = logger; 121 } 122 123 @Override 124 public void service(ServiceManager manager) throws ServiceException 125 { 126 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 127 _cTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 128 _tagProviderExtPt = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 129 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 130 _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE); 131 } 132 133 /* ------------------------ */ 134 /* Content methods */ 135 /* ------------------------ */ 136 137 /** 138 * Get the content types of a content 139 * @param contentId The content id 140 * @return The content type or empty if the content does not exist 141 */ 142 public static NodeList contentTypes(String contentId) 143 { 144 ArrayList<StringElement> contentTypes = new ArrayList<>(); 145 146 try 147 { 148 Content content = _ametysObjectResolver.resolveById(contentId); 149 150 try 151 { 152 for (String id : content.getTypes()) 153 { 154 contentTypes.add(new StringElement("content-type", "id", id)); 155 } 156 } 157 catch (AmetysRepositoryException e) 158 { 159 _logger.error("Can not get type of content with id '" + contentId + "'", e); 160 } 161 } 162 catch (UnknownAmetysObjectException e) 163 { 164 _logger.error("Can not get type of content with id '" + contentId + "'", e); 165 } 166 167 return new AmetysNodeList(contentTypes); 168 } 169 170 /** 171 * Get the mixins of a content 172 * @param contentId The content id 173 * @return The content type or empty if the content does not exist 174 */ 175 public static NodeList contentMixinTypes(String contentId) 176 { 177 ArrayList<StringElement> contentTypes = new ArrayList<>(); 178 179 try 180 { 181 Content content = _ametysObjectResolver.resolveById(contentId); 182 183 try 184 { 185 for (String id : content.getMixinTypes()) 186 { 187 contentTypes.add(new StringElement("mixin", "id", id)); 188 } 189 } 190 catch (AmetysRepositoryException e) 191 { 192 _logger.error("Can not get type of content with id '" + contentId + "'", e); 193 } 194 } 195 catch (UnknownAmetysObjectException e) 196 { 197 _logger.error("Can not get type of content with id '" + contentId + "'", e); 198 } 199 200 return new AmetysNodeList(contentTypes); 201 } 202 203 /** 204 * Determines if the content of given id is a entry of reference table 205 * @param contentId the content id 206 * @return true if the content type is a reference table 207 */ 208 public static boolean isReferenceTableContent(String contentId) 209 { 210 try 211 { 212 Content content = _ametysObjectResolver.resolveById(contentId); 213 return _contentHelper.isReferenceTable(content); 214 } 215 catch (UnknownAmetysObjectException e) 216 { 217 _logger.error("Can not get type of unknown content with id '" + contentId + "'", e); 218 return false; 219 } 220 } 221 222 /** 223 * Returns the current language for rendering. 224 * @return the current language for rendering. 225 */ 226 public static String lang() 227 { 228 Request request = ContextHelper.getRequest(_context); 229 return (String) request.getAttribute("renderingLanguage"); 230 } 231 232 /** 233 * Determines if there is a non-empty value for the data at the given path 234 * @param contentId The content id 235 * @param dataPath the path of data 236 * @return true if the data exists 237 */ 238 public static boolean hasValue(String contentId, String dataPath) 239 { 240 try 241 { 242 if (StringUtils.isEmpty(contentId) || StringUtils.isEmpty(dataPath)) 243 { 244 if (_logger.isDebugEnabled()) 245 { 246 _logger.debug("Can not check if content has a non-empty value: mandatory arguments content's id and/or attribute path are missing (" + contentId + ", " + dataPath + ")"); 247 } 248 return false; 249 } 250 251 Content content = _ametysObjectResolver.resolveById(contentId); 252 return content.hasValue(dataPath); 253 } 254 catch (UnknownAmetysObjectException | BadDataPathCardinalityException e) 255 { 256 if (_logger.isDebugEnabled()) 257 { 258 _logger.debug("Can not check if attribute at path '" + dataPath + "' exists and is not empty on content with id '" + contentId + "'", e); 259 } 260 return false; 261 } 262 } 263 264 /** 265 * Get the attribute of a content at the given path 266 * @param contentId The content id 267 * @param dataPath The data path 268 * @return The value into a "value" node or null if an error occurred 269 */ 270 public static NodeList contentAttribute(String contentId, String dataPath) 271 { 272 return contentAttribute(contentId, dataPath, null); 273 } 274 275 /** 276 * Get the attribute of a content at the given path 277 * @param contentId The content id 278 * @param dataPath The data path 279 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 280 * @return The value into a "value" node or null if an error occurred 281 */ 282 public static NodeList contentAttribute(String contentId, String dataPath, String lang) 283 { 284 try 285 { 286 Content content = _ametysObjectResolver.resolveById(contentId); 287 DataContext context = RepositoryDataContext.newInstance() 288 .withObject(content); 289 290 List<Node> values = _getNodeValues(content.getDataHolder(), dataPath, lang, context); 291 if (values != null) 292 { 293 return new AmetysNodeList(values); 294 } 295 } 296 catch (UnknownAmetysObjectException e) 297 { 298 _logger.error("Can not get attribute at path '" + dataPath + "' on unknown content with id '" + contentId + "'", e); 299 } 300 301 return null; 302 } 303 304 /** 305 * Get values of an attribute of a model aware data holder at the given path 306 * @param dataHolder the data holder 307 * @param dataPath The data path 308 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 309 * @return A Node for each values or null if an error occurred 310 */ 311 protected static List<Node> _getNodeValues(ModelAwareDataHolder dataHolder, String dataPath, String lang) 312 { 313 return _getNodeValues(dataHolder, dataPath, lang, DataContext.newInstance()); 314 } 315 316 /** 317 * Get values of an attribute of a model aware data holder at the given path 318 * @param dataHolder the data holder 319 * @param dataPath The data path 320 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 321 * @param dataContext The data context 322 * @return A Node for each values or null if an error occurred 323 */ 324 protected static List<Node> _getNodeValues(ModelAwareDataHolder dataHolder, String dataPath, String lang, DataContext dataContext) 325 { 326 327 if (dataHolder == null) 328 { 329 return null; 330 } 331 332 try 333 { 334 SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 335 TransformerHandler th = saxTransformerFactory.newTransformerHandler(); 336 337 DOMResult result = new DOMResult(); 338 th.setResult(result); 339 340 th.startDocument(); 341 XMLUtils.startElement(th, "value"); 342 343 Locale locale = StringUtils.isEmpty(lang) ? null : new Locale(lang); 344 dataHolder.dataToSAX(th, dataPath, dataContext.cloneContext().withLocale(locale).withEmptyValues(false)); 345 346 XMLUtils.endElement(th, "value"); 347 th.endDocument(); 348 349 List<Node> values = new ArrayList<>(); 350 351 // #getChildNodes() returns a NodeList that contains the value(s) saxed 352 // we cannot returns directly this NodeList because saxed values should be wrapped into a <value> tag. 353 NodeList childNodes = result.getNode().getFirstChild().getChildNodes(); 354 for (int i = 0; i < childNodes.getLength(); i++) 355 { 356 Node n = childNodes.item(i); 357 values.add(n); 358 } 359 360 return values; 361 } 362 catch (BadDataPathCardinalityException e) 363 { 364 _logger.error("Unable to get attribute at path '" + dataPath + "'. Path is invalid.", e); 365 } 366 catch (TransformerConfigurationException | SAXException e) 367 { 368 _logger.error("Fail to sax attribute at path '" + dataPath + "'", e); 369 } 370 catch (Exception e) 371 { 372 _logger.error("An error occurred, impossible to get attribute at path '" + dataPath + "'", e); 373 } 374 375 return null; 376 } 377 378 /** 379 * Get the metadata of a content 380 * @param contentId The content id 381 * @param metadataName The metadata name (/ for composite) 382 * @param lang The language for localized metadata. Can be null to get the current language. 383 * @return The name or empty if the metadata or the content does not exist 384 */ 385 public static String contentMetadata(String contentId, String metadataName, String lang) 386 { 387 try 388 { 389 Content content = _ametysObjectResolver.resolveById(contentId); 390 try 391 { 392 Locale locale = StringUtils.isEmpty(lang) ? null : new Locale(lang); 393 return _getMetadata(content.getMetadataHolder(), metadataName, locale); 394 } 395 catch (UnknownMetadataException e) 396 { 397 _logger.debug("Can not get metadata '" + metadataName + "' on content with id '" + contentId + "'", e); 398 return ""; 399 } 400 } 401 catch (UnknownAmetysObjectException e) 402 { 403 _logger.debug("Can not get metadata '" + metadataName + "' on unknown content with id '" + contentId + "'", e); 404 return ""; 405 } 406 } 407 408 /** 409 * Get the metadata of a content 410 * @param contentId The content id 411 * @param metadataName The metadata name (/ for composite) 412 * @return The name or empty if the metadata or the content does not exist 413 */ 414 public static String contentMetadata(String contentId, String metadataName) 415 { 416 return contentMetadata(contentId, metadataName, null); 417 } 418 419 private static String _getMetadata(CompositeMetadata cm, String metadataName, Locale locale) 420 { 421 int i = metadataName.indexOf("/"); 422 if (i == -1) 423 { 424 if (cm.getType(metadataName).equals(MetadataType.MULTILINGUAL_STRING)) 425 { 426 if (locale == null) 427 { 428 String currentLanguage = lang(); 429 if (StringUtils.isEmpty(currentLanguage)) 430 { 431 _logger.error("Can not get the value of a multilingual metadata " + metadataName + " without a defined locale"); 432 return ""; 433 } 434 return cm.getLocalizedString(metadataName, new Locale(currentLanguage)); 435 } 436 else 437 { 438 return cm.getLocalizedString(metadataName, locale); 439 } 440 } 441 else 442 { 443 return cm.getString(metadataName); 444 } 445 } 446 else 447 { 448 return _getMetadata(cm.getCompositeMetadata(metadataName.substring(0, i)), metadataName.substring(i + 1), locale); 449 } 450 } 451 452 /** 453 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of the current content. 454 * @return an Element containing the attachments of the current content as {@link Resource}s. 455 */ 456 public static Node contentAttachments() 457 { 458 Request request = ContextHelper.getRequest(_context); 459 460 Content content = (Content) request.getAttribute(Content.class.getName()); 461 462 return contentAttachments(content); 463 } 464 465 /** 466 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content. 467 * @param contentId the content ID. 468 * @return an Element containing the attachments of the given content as {@link Resource}s. 469 */ 470 public static Node contentAttachments(String contentId) 471 { 472 Content content = _ametysObjectResolver.resolveById(contentId); 473 474 return contentAttachments(content); 475 } 476 477 /** 478 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content. 479 * @param content the content. 480 * @return an Element containing the attachments of the given content as {@link Resource}s. 481 */ 482 private static Node contentAttachments(Content content) 483 { 484 if (content == null) 485 { 486 return null; 487 } 488 489 ResourceCollection collection = content.getRootAttachments(); 490 491 return collection != null ? new ResourceCollectionElement(collection) : new EmptyElement("collection"); 492 } 493 494 /** 495 * Set the content of given id in request attribute 496 * @param contentId the id of content 497 */ 498 public static void setCurrentContent(String contentId) 499 { 500 setCurrentContent(contentId, null); 501 } 502 503 /** 504 * Set the content of given id and version in request attribute 505 * @param contentId the id of content 506 * @param versionLabel The version label 507 */ 508 public static void setCurrentContent(String contentId, String versionLabel) 509 { 510 Request request = ContextHelper.getRequest(_context); 511 512 Content content = _ametysObjectResolver.resolveById(contentId); 513 514 if (StringUtils.isNotEmpty(versionLabel) && content instanceof VersionAwareAmetysObject) 515 { 516 String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels(); 517 if (ArrayUtils.contains(allLabels, versionLabel)) 518 { 519 ((VersionAwareAmetysObject) content).switchToLabel(versionLabel); 520 } 521 } 522 523 request.setAttribute(Content.class.getName(), content); 524 525 } 526 527 //************************* 528 // Tag methods 529 //************************* 530 531 /** 532 * Returns all tags of the current content. 533 * @return a list of tags. 534 */ 535 public static NodeList contentTags() 536 { 537 Request request = ContextHelper.getRequest(_context); 538 539 Content content = (Content) request.getAttribute(Content.class.getName()); 540 return _contentTags(content); 541 } 542 543 /** 544 * Returns all tags of the given content 545 * @param contentId The identifier of the content 546 * @return a list of tags. 547 */ 548 public static NodeList contentTags(String contentId) 549 { 550 try 551 { 552 Content content = _ametysObjectResolver.resolveById(contentId); 553 return _contentTags(content); 554 } 555 catch (AmetysRepositoryException e) 556 { 557 _logger.warn("Cannot get tags for content '" + contentId + "'", e); 558 } 559 560 return null; 561 } 562 563 /** 564 * Returns all tags of the given content 565 * @param content The content 566 * @return a list of tags. 567 */ 568 protected static NodeList _contentTags(Content content) 569 { 570 if (content == null) 571 { 572 return null; 573 } 574 575 List<TagElement> list = new ArrayList<>(); 576 577 for (String tag : content.getTags()) 578 { 579 list.add(new TagElement(tag)); 580 } 581 582 return new AmetysNodeList(list); 583 } 584 585 /** 586 * Get the name of the parent of a tag. 587 * @param siteName the site name 588 * @param tagName the tag's name 589 * @return The id of parent or empty if not found 590 */ 591 public static String tagParent(String siteName, String tagName) 592 { 593 Map<String, Object> contextParameters = new HashMap<>(); 594 contextParameters.put("siteName", siteName); 595 596 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 597 if (tag == null) 598 { 599 return StringUtils.EMPTY; 600 } 601 602 String parentName = tag.getParentName(); 603 return parentName != null ? parentName : StringUtils.EMPTY; 604 } 605 606 /** 607 * Get the path of a tag. The path contains the tag's parents seprated by '/'. 608 * @param siteName The site name 609 * @param tagName The unique tag's name 610 * @return The tag's path or empty string if tag does not exist 611 */ 612 public static String tagPath (String siteName, String tagName) 613 { 614 Map<String, Object> contextParameters = new HashMap<>(); 615 contextParameters.put("siteName", siteName); 616 617 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 618 if (tag == null) 619 { 620 return StringUtils.EMPTY; 621 } 622 623 String path = tagName; 624 625 Tag parentTag = tag.getParent(); 626 while (parentTag != null) 627 { 628 path = parentTag.getName() + "/" + path; 629 parentTag = parentTag.getParent(); 630 } 631 632 return path; 633 } 634 635 /** 636 * Get the label of a tag 637 * @param siteName the current site 638 * @param tagName the name of the tag 639 * @param lang the lang (if i18n tag) 640 * @return the label of the tag or empty if it cannot be found 641 */ 642 public static String tagLabel(String siteName, String tagName, String lang) 643 { 644 Map<String, Object> contextParameters = new HashMap<>(); 645 contextParameters.put("siteName", siteName); 646 647 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 648 return tag == null ? "" : _i18nUtils.translate(tag.getTitle(), lang); 649 } 650 651 /** 652 * Get the description of a tag 653 * @param siteName the current site 654 * @param tagName the name of the tag 655 * @param lang the lang (if i18n tag) 656 * @return the label of the tag or empty if it cannot be found 657 */ 658 public static String tagDescription(String siteName, String tagName, String lang) 659 { 660 Map<String, Object> contextParameters = new HashMap<>(); 661 contextParameters.put("siteName", siteName); 662 663 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 664 return tag == null ? "" : _i18nUtils.translate(tag.getDescription(), lang); 665 } 666 667 /** 668 * Get the visibility of a tag 669 * @param siteName the current site 670 * @param tagName the name of the tag 671 * @return the lower-cased visibility of the tag ("public" or "private") 672 */ 673 public static String tagVisibility(String siteName, String tagName) 674 { 675 Map<String, Object> contextParameters = new HashMap<>(); 676 contextParameters.put("siteName", siteName); 677 678 CMSTag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 679 return tag == null ? "" : tag.getVisibility().toString().toLowerCase(); 680 } 681 682 /** 683 * Get the color (main and text) of a tag 684 * @param siteName the current site 685 * @param tagName the name of the tag 686 * @return the the color (main and text) of a tag 687 */ 688 public static MapElement tagColor(String siteName, String tagName) 689 { 690 Map<String, Object> contextParameters = new HashMap<>(); 691 contextParameters.put("siteName", siteName); 692 693 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 694 if (tag instanceof ColorableTag colorTag) 695 { 696 String color = colorTag.getColor(true); 697 Map<String, String> map = colorTag.getColorComponent().getColors().get(color); 698 return map != null ? new MapElement("color", map) : null; 699 } 700 701 return null; 702 } 703 704 /* ----------------------------- */ 705 /* Content type methods */ 706 /* ----------------------------- */ 707 708 /** 709 * Returns all tags of a content type 710 * @param contentTypeId The id of the content type 711 * @return a list of tags. 712 */ 713 public static NodeList contentTypeTags(String contentTypeId) 714 { 715 ArrayList<TagElement> tags = new ArrayList<>(); 716 717 try 718 { 719 ContentType cType = _cTypeExtensionPoint.getExtension(contentTypeId); 720 if (cType != null) 721 { 722 for (String tag : cType.getTags()) 723 { 724 tags.add(new TagElement(tag)); 725 } 726 } 727 else 728 { 729 _logger.error("Can not get tags of unknown content type of id '" + contentTypeId + "'"); 730 } 731 732 } 733 catch (AmetysRepositoryException e) 734 { 735 _logger.error("Can not get tags of content type of id '" + contentTypeId + "'", e); 736 } 737 738 return new AmetysNodeList(tags); 739 } 740 741 /** 742 * Get the excerpt of content from the given richtext attribute 743 * @param contentId the id of content 744 * @param attributePath the attribute path of rich text attribute 745 * @param limit the max length for content excerpt 746 * @return the excerpt 747 */ 748 public static String contentExcerpt(String contentId, String attributePath, int limit) 749 { 750 Content content = _ametysObjectResolver.resolveById(contentId); 751 752 if (content.hasValue(attributePath)) 753 { 754 RichText richText = content.getValue(attributePath); 755 return _richTextHelper.richTextToString(richText, limit); 756 } 757 758 return org.apache.commons.lang3.StringUtils.EMPTY; 759 } 760 761 /** 762 * Get the HTML view of a content 763 * @param contentId the id of content 764 * @return the content html view wrapped into a <content> tag 765 */ 766 public static Node getContentView(String contentId) 767 { 768 return getContentView(contentId, null, 1, null, false); 769 } 770 771 /** 772 * Get the HTML view of a content with offset on headings 773 * @param contentId the id of content 774 * @param startHeadingsLevel The start level for headings (h1, h2, h3, ...). For example, set to 2 so that the highest level headings are <h2>. Set to 1 for no offset. 775 * @return the content html view wrapped into a <content> tag 776 */ 777 public static Node getContentView(String contentId, int startHeadingsLevel) 778 { 779 return getContentView(contentId, null, startHeadingsLevel, null, false); 780 } 781 782 /** 783 * Get the HTML view of a content 784 * @param contentId the id of content 785 * @param viewName The content view name 786 * @return the content html view wrapped into a <content> tag 787 */ 788 public static Node getContentView(String contentId, String viewName) 789 { 790 return getContentView(contentId, viewName, 1, null, false); 791 } 792 793 /** 794 * Get the HTML view of a content 795 * @param contentId the id of content 796 * @param viewName The content view name 797 * @param startHeadingsLevel The start level for headings (h1, h2, h3, ...). For example, set to 2 so that the highest level headings are <h2>. Set to 1 for no offset. 798 * @return the content html view wrapped into a <content> tag 799 */ 800 public static Node getContentView(String contentId, String viewName, int startHeadingsLevel) 801 { 802 return getContentView(contentId, viewName, startHeadingsLevel, null, false); 803 } 804 805 /** 806 * Get the HTML view of a content with offset on headings 807 * @param contentId the id of content 808 * @param viewName The content view name 809 * @param lang the language. Can be null. Useful only if the content has a multilingual title. 810 * @param startHeadingsLevel The start level for headings (h1, h2, h3, ...). For example, set to 2 so that the highest level headings are <h2>. Set to 1 for no offset. 811 * @param checkReadAccess Set to <code>true</code> to check the read access on content. Be careful, do not use with <code>true</code> on a cacheable element. 812 * @return the content html view wrapped into a <content> tag 813 */ 814 public static Node getContentView(String contentId, String viewName, int startHeadingsLevel, String lang, boolean checkReadAccess) 815 { 816 Content content = _ametysObjectResolver.resolveById(contentId); 817 818 if (checkReadAccess && !_rightManager.currentUserHasReadAccess(content)) 819 { 820 _logger.warn("Current user is not authorized to see content of id '" + contentId + "'. AmetysXSLHelper#getContentView will return null element"); 821 return null; 822 } 823 824 Locale requestedLocale = StringUtils.isNotEmpty(lang) ? new Locale(lang) : null; 825 826 DocumentBuilder builder = null; 827 Source source = null; 828 829 String uri = _contentHelper.getContentHtmlViewUrl(content, viewName); 830 831 try 832 { 833 source = _sourceResolver.resolveURI(uri); 834 835 Source src = null; 836 try 837 { 838 // Wrap HTML view into a <content> tag and move titles hierarchy if needed 839 src = _sourceResolver.resolveURI("plugin:cms://stylesheets/content/content2htmlview.xsl"); 840 try (InputStream is = src.getInputStream()) 841 { 842 SAXTransformerFactory tFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 843 844 // Set uri resolver to resolve import 845 tFactory.setURIResolver(new URIResolver() 846 { 847 public javax.xml.transform.Source resolve(String href, String base) throws TransformerException 848 { 849 try 850 { 851 Source resolvedSource = _sourceResolver.resolveURI(href); 852 return new StreamSource(resolvedSource.getInputStream()); 853 } 854 catch (IOException e) 855 { 856 throw new TransformerException(e); 857 } 858 } 859 }); 860 861 TransformerHandler transformerHandler = tFactory.newTransformerHandler(new StreamSource(is)); 862 Transformer transformer = transformerHandler.getTransformer(); 863 864 Properties format = new Properties(); 865 format.put(OutputKeys.METHOD, "xml"); 866 format.put(OutputKeys.ENCODING, "UTF-8"); 867 868 transformer.setOutputProperties(format); 869 870 transformer.setParameter("contentId", content.getId()); 871 transformer.setParameter("contentName", content.getName()); 872 transformer.setParameter("contentTitle", content.getTitle(requestedLocale)); 873 if (content.getLanguage() != null) 874 { 875 transformer.setParameter("contentLanguage", content.getLanguage()); 876 } 877 transformer.setParameter("headingLevel", startHeadingsLevel); 878 879 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 880 Document document = builder.newDocument(); 881 DOMResult result = new DOMResult(document); 882 883 transformerHandler.setResult(result); 884 SourceUtil.toSAX(source, transformerHandler); 885 886 return result.getNode(); 887 } 888 } 889 finally 890 { 891 _sourceResolver.release(src); 892 } 893 } 894 catch (Exception e) 895 { 896 _logger.error("Fail to get HTML view of content " + contentId, e); 897 return null; 898 } 899 finally 900 { 901 _sourceResolver.release(source); 902 } 903 } 904}