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