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