/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.structure.menuitem; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import org.jdom.Document; import org.jdom.Element; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import com.enonic.cms.framework.xml.XMLBuilder; import com.enonic.cms.framework.xml.XMLDocument; import com.enonic.cms.framework.xml.XMLDocumentFactory; import com.enonic.cms.core.content.ContentEntity; import com.enonic.cms.core.content.contenttype.ContentTypeEntity; import com.enonic.cms.core.language.LanguageEntity; import com.enonic.cms.core.language.LanguageKey; import com.enonic.cms.core.structure.page.PageEntity; import com.enonic.cms.core.structure.page.template.PageTemplateEntity; public class MenuItemXmlCreator { private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormat.forPattern( "yyyy-MM-dd HH:mm" ); private MenuItemXMLCreatorSetting setting; private MenuItemAccessResolver menuItemAccessResolver; private MenuItemAccessRightsAccumulatedXmlCreator accessRightsAccumulatedXmlCreator = new MenuItemAccessRightsAccumulatedXmlCreator(); private boolean includeSiteInfo = false; private boolean includePathInfo = false; private boolean includePageTemplateName = false; private boolean includeAnonynousReadInfo = false; private boolean includeUserAccessRightsInfo = false; private final MenuItemEntity menuItemInPreview; public MenuItemXmlCreator( MenuItemXMLCreatorSetting setting, MenuItemAccessResolver menuItemAccessResolver, final MenuItemEntity menuItemInPreview ) { this.setting = setting; this.menuItemAccessResolver = menuItemAccessResolver; this.menuItemInPreview = menuItemInPreview; } public MenuItemXmlCreator( MenuItemXMLCreatorSetting setting, MenuItemAccessResolver menuItemAccessResolver ) { this.setting = setting; this.menuItemAccessResolver = menuItemAccessResolver; this.menuItemInPreview = null; } public void setIncludeAnonynousReadInfo( boolean value ) { this.includeAnonynousReadInfo = value; } public void setIncludeUserAccessRightsInfo( boolean includeUserAccessRightsInfo ) { this.includeUserAccessRightsInfo = includeUserAccessRightsInfo; } public Element createMenuItemElement( final MenuItemEntity menuItem ) { final XMLBuilder xmlDoc = new XMLBuilder( "menuitems" ); doAddMenuItemElement( xmlDoc, replaceWithPreview( menuItem ), false, 0 ); return (Element) xmlDoc.getRootElement().getChild( "menuitem" ).detach(); } public Element createMenuItemElement( MenuItemEntity menuItem, MenuItemAccumulatedAccessRights accessRightsForUser ) { return createMenuItemElement( replaceWithPreview( menuItem ), accessRightsForUser, null ); } public Element createMenuItemElement( MenuItemEntity menuItem, MenuItemAccumulatedAccessRights accessRightsForUser, MenuItemAccumulatedAccessRights accesRightsForAnonymous ) { XMLBuilder xmlDoc = new XMLBuilder( "menuitems" ); doAddMenuItemElement( xmlDoc, menuItem, false, 0 ); final Element menuItemEl = (Element) xmlDoc.getRootElement().getChild( "menuitem" ).detach(); if ( includeAnonynousReadInfo ) { menuItemEl.setAttribute( "anonread", accesRightsForAnonymous.isReadAccess() ? "true" : "false" ); } if ( includeUserAccessRightsInfo ) { accessRightsAccumulatedXmlCreator.setUserRightAttributes( menuItemEl, accessRightsForUser ); } return menuItemEl; } public XMLDocument createLegacyGetMenuItem( MenuItemEntity menuItem ) { if ( menuItem == null ) { XMLBuilder xmlDoc = new XMLBuilder( "menuitems" ); return xmlDoc.getDocument(); } menuItem = replaceWithPreview( menuItem ); // forcing mandantory settings setting.menuItemLevels = 1; setting.includeTypeSpecificXML = true; setting.includeDocumentElement = true; setting.includeChildren = false; // checking access rights first, returning empty menuitems element if no read right if ( !menuItemAccessResolver.hasAccess( setting.user, menuItem, MenuItemAccessType.READ ) ) { XMLBuilder xmlDoc = new XMLBuilder( "menuitems" ); xmlDoc.setAttribute( "istop", menuItem.isAtTopLevel() ? "yes" : "no" ); return xmlDoc.getDocument(); } // simple handling if parents is not wanted if ( !setting.includeParents ) { XMLBuilder xmlDoc = new XMLBuilder( "menuitems" ); xmlDoc.setAttribute( "istop", menuItem.isAtTopLevel() ? "yes" : "no" ); addMenuItemElement( xmlDoc, menuItem, false ); return xmlDoc.getDocument(); } // special handling if parents wanted... List<MenuItemEntity> menuItemPath = menuItem.getMenuItemPath(); // build one document for each menuitem in path List<Document> xmlDocs = new ArrayList<Document>(); for ( MenuItemEntity curMenuItem : menuItemPath ) { XMLBuilder xmlDoc = new XMLBuilder( "menuitems" ); xmlDoc.setAttribute( "istop", curMenuItem.isAtTopLevel() ? "yes" : "no" ); addMenuItemElement( xmlDoc, curMenuItem, false ); xmlDocs.add( xmlDoc.getRootDocument() ); } // merge the menuitems into one doc (the doc in position 0) while ( xmlDocs.size() > 1 ) { insertLastMenuItemAsASubnode( xmlDocs ); } return XMLDocumentFactory.create( xmlDocs.get( 0 ) ); } private void insertLastMenuItemAsASubnode( List<Document> xmlDocs ) { Document last = xmlDocs.get( xmlDocs.size() - 1 ); xmlDocs.remove( last ); Document parent = xmlDocs.get( xmlDocs.size() - 1 ); Element rootLast = last.getRootElement(); Element parentRoot = parent.getRootElement(); Element parentMenuItem = parentRoot.getChild( "menuitem" ); parentMenuItem.removeChild( "menuitems" ); parentMenuItem.addContent( rootLast.detach() ); } public void addMenuItemElement( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { doAddMenuItemElement( xmlDoc, replaceWithPreview( menuItem ), true, 0 ); } public void addMenuItemElement( XMLBuilder xmlDoc, MenuItemEntity menuItem, boolean includeChildren ) { doAddMenuItemElement( xmlDoc, replaceWithPreview( menuItem ), includeChildren, 0 ); } private void doAddMenuItemElement( XMLBuilder xmlDoc, MenuItemEntity menuItem, boolean includeChildren, int menuItemLevelsWalked ) { xmlDoc.startElement( "menuitem" ); xmlDoc.setAttribute( "key", menuItem.getKey().toInt() ); xmlDoc.setAttribute( "menukey", menuItem.getSite().getKey().toString() ); xmlDoc.setAttribute( "modifier", menuItem.getModifier().getKey().toString() ); xmlDoc.setAttribute( "order", menuItem.getOrder() ); xmlDoc.setAttribute( "owner", menuItem.getOwner().getKey().toString() ); xmlDoc.setAttribute( "timestamp", TIMESTAMP_FORMATTER.print( menuItem.getTimestamp().getTime() ) ); xmlDoc.setAttribute( "type", menuItem.getType().getName() ); xmlDoc.setAttribute( "visible", menuItem.getHidden() ? "no" : "yes" ); if ( menuItem.getParent() != null ) { xmlDoc.setAttribute( "parent", menuItem.getParent().getKey().toInt() ); } if ( menuItem.getMenuItemShortcut() != null ) { xmlDoc.setAttribute( "shortcut", menuItem.getMenuItemShortcut().getKey().toInt() ); } if ( includeSiteInfo ) { xmlDoc.setAttribute( "site-name", menuItem.getSite().getName() ); } addLanguageAttributes( xmlDoc, menuItem ); xmlDoc.addContentElement( "name", menuItem.getName() ); xmlDoc.addContentElement( "menu-name", asEmptyIfNull( menuItem.getMenuName() ) ); xmlDoc.addContentElement( "display-name", asEmptyIfNull( menuItem.getDisplayName() ) ); xmlDoc.addContentElement( "show-in-menu", menuItem.getHidden() ? "false" : "true" ); xmlDoc.addContentElement( "description", menuItem.getDescription() ); xmlDoc.addContentElement( "keywords", menuItem.getKeywords() ); if ( includePathInfo ) { xmlDoc.startElement( "path" ); xmlDoc.addContent( menuItem.getPathAsString() ); xmlDoc.endElement(); } addFromXmlData( xmlDoc, menuItem ); addTypeSpecificXml( xmlDoc, menuItem ); addMenuItemsElement( xmlDoc, menuItem, includeChildren, menuItemLevelsWalked + 1 ); if ( setting.activeMenuItem != null ) { if ( menuItem.getKey().toInt() == setting.activeMenuItem.getKey().toInt() ) { xmlDoc.setAttribute( "active", "true" ); xmlDoc.setAttribute( "path", "true" ); } if ( menuItem.isParentOf( setting.activeMenuItem ) ) { xmlDoc.setAttribute( "path", "true" ); } } xmlDoc.endElement(); } private void addLanguageAttributes( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { LanguageEntity language = menuItem.getLanguage(); if ( language == null ) { return; } xmlDoc.setAttribute( "language", language.getDescription() ); xmlDoc.setAttribute( "languagecode", language.getCode() ); LanguageKey languageKey = language.getKey(); xmlDoc.setAttribute( "languagekey", languageKey != null ? languageKey.toString() : "" ); } private void addFromXmlData( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { if ( !menuItem.hasXmlData() ) { xmlDoc.startElement( "parameters" ); addShortcutAsParameter( menuItem, xmlDoc.getCurrentElement() ); xmlDoc.endElement(); return; } Document dataDoc = menuItem.getXmlDataAsClonedJDomDocument(); Element dataEl = dataDoc.getRootElement(); // parameters element Element parametersEl = dataEl.getChild( "parameters" ); if ( parametersEl != null ) { addShortcutAsParameter( menuItem, parametersEl ); addParametersElement( xmlDoc, (Element) parametersEl.detach() ); } else { xmlDoc.startElement( "parameters" ); addShortcutAsParameter( menuItem, xmlDoc.getCurrentElement() ); xmlDoc.endElement(); } // document element Element documentEl = dataEl.getChild( "document" ); if ( documentEl != null ) { documentEl.detach(); if ( setting.includeDocumentElement ) { addDocumentElement( xmlDoc, (Element) documentEl.detach() ); } } // data element addDataElement( xmlDoc, (Element) dataEl.detach() ); } private void addShortcutAsParameter( final MenuItemEntity menuItem, final Element parametersEl ) { if ( menuItem.getMenuItemShortcut() != null ) { Element element = new Element( "parameter" ); element.setAttribute( "name", "key" ); element.setAttribute( "override", "false" ); element.setText( menuItem.getMenuItemShortcut().getKey().toString() ); parametersEl.addContent( 0, element ); } } private void addDataElement( XMLBuilder xmlDoc, Element dataEl ) { Element menuItemElement = xmlDoc.getCurrentElement(); menuItemElement.addContent( dataEl.detach() ); } private void addParametersElement( XMLBuilder xmlDoc, Element parametersEl ) { Element menuItemElement = xmlDoc.getCurrentElement(); menuItemElement.addContent( parametersEl ); } private void addDocumentElement( XMLBuilder xmlDoc, Element documentEl ) { Element menuItemElement = xmlDoc.getCurrentElement(); menuItemElement.addContent( documentEl ); documentEl.setAttribute( "mode", "xhtml" ); } private void addTypeSpecificXml( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { int type = menuItem.getType().getKey(); // special handling of type url, cause it has to be displayed whatever is set in includeTypeSpecificXML if ( type == 2 ) { addUrlElement( xmlDoc, menuItem ); return; } if ( setting.includeTypeSpecificXML ) { // build type-specific XML: switch ( type ) { case 1: // page addPageElement( xmlDoc, menuItem ); break; //case 2: // url // addUrlElement(xmlDoc, menuItem); // break; case 4:// content addSectionElement( xmlDoc, menuItem ); addPageElement( xmlDoc, menuItem ); addContentKeyAttribute( xmlDoc, menuItem ); break; case 5: // label break; case 6:// section addSectionElement( xmlDoc, menuItem ); break; case 7:// shortcut addShortcutElement( xmlDoc, menuItem ); break; } } } private void addContentKeyAttribute( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { ContentEntity content = menuItem.getContent(); if ( content != null ) { xmlDoc.setAttribute( "contentkey", String.valueOf( content.getKey() ) ); } } private void addUrlElement( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { int menuItemType = menuItem.getType().getKey(); if ( menuItemType != 2 ) { return; } String url = menuItem.getUrl(); if ( url == null ) { return; } xmlDoc.startElement( "url" ); xmlDoc.setAttribute( "newwindow", menuItem.isOpenNewWindowForURL() ? "yes" : "no" ); xmlDoc.addContent( url ); xmlDoc.endElement(); } protected void addShortcutElement( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { int menuItemType = menuItem.getType().getKey(); if ( menuItemType != 7 ) { return; } MenuItemEntity shortcutDestination = menuItem.getMenuItemShortcut(); if ( shortcutDestination == null ) { return; } xmlDoc.startElement( "shortcut" ); xmlDoc.setAttribute( "key", shortcutDestination.getKey().toInt() ); xmlDoc.setAttribute( "name", shortcutDestination.getName() ); xmlDoc.setAttribute( "forward", menuItem.isShortcutForward() ? "true" : "false" ); xmlDoc.endElement(); } /** * Creates the <code>page</code> node, based on the page entity in the menuitem. * * @param xmlDoc The XML which the page is added to. * @param menuItem The menu item that holds the page node. */ private void addPageElement( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { PageEntity page = menuItem.getPage(); if ( page == null ) { return; } PageTemplateEntity pageTemplate = page.getTemplate(); xmlDoc.startElement( "page" ); xmlDoc.setAttribute( "key", page.getKey() ); xmlDoc.setAttribute( "pagetemplatekey", Integer.toString( pageTemplate.getKey() ) ); xmlDoc.setAttribute( "pagetemplate", pageTemplate.getName() ); xmlDoc.setAttribute( "pagetemplatetype", pageTemplate.getType().getKey() ); if ( includePageTemplateName ) { xmlDoc.setAttribute( "pagetemplatename", pageTemplate.getName() ); } xmlDoc.endElement(); } /** * Gets the section of a menuItem, and if it's not <code>null</code>, it's added to the XML. * * @param xmlDoc The XML document to add the section too. * @param menuItem The menuItem containing the section. */ private void addSectionElement( XMLBuilder xmlDoc, MenuItemEntity menuItem ) { if ( !menuItem.isSection() ) { return; } xmlDoc.startElement( "section" ); xmlDoc.setAttribute( "key", menuItem.getKey().toInt() ); xmlDoc.setAttribute( "menuitemkey", menuItem.getKey().toInt() ); xmlDoc.setAttribute( "menukey", menuItem.getSite().getKey().toString() ); xmlDoc.setAttribute( "ordered", menuItem.isOrderedSection() ? "true" : "false" ); xmlDoc.startElement( "contenttypes" ); Set<ContentTypeEntity> contentTypes = menuItem.getAllowedSectionContentTypes(); for ( ContentTypeEntity contentType : contentTypes ) { xmlDoc.startElement( "contenttype" ); xmlDoc.setAttribute( "key", contentType.getKey() ); xmlDoc.startElement( "name" ); xmlDoc.addContent( contentType.getName() ); xmlDoc.endElement(); xmlDoc.endElement(); } xmlDoc.endElement(); xmlDoc.endElement(); } private void addMenuItemsElement( XMLBuilder xmlDoc, MenuItemEntity menuItem, boolean includeChildren, int menuItemLevelsWalked ) { if ( !setting.includeChildren ) { return; } xmlDoc.startElement( "menuitems" ); xmlDoc.setAttribute( "istop", "no" ); final Collection<MenuItemEntity> children = menuItem.getChildren(); xmlDoc.setAttribute( "child-count", String.valueOf( countNumberOfAccessibleChildren( children ) ) ); final boolean stillMoreLevelsToWalk = menuItemLevelsWalked + 1 <= setting.menuItemLevels; final boolean walkAllLevels = setting.menuItemLevels == 0; if ( includeChildren && ( walkAllLevels || stillMoreLevelsToWalk ) ) { if ( !children.isEmpty() ) { for ( MenuItemEntity child : children ) { if ( addable( child ) ) { doAddMenuItemElement( xmlDoc, replaceWithPreview( child ), includeChildren, menuItemLevelsWalked ); } } } } xmlDoc.endElement(); } private MenuItemEntity replaceWithPreview( final MenuItemEntity menuItem ) { if ( menuItem == null ) { return null; } else if ( menuItemInPreview == null ) { return menuItem; } else if ( menuItem == menuItemInPreview ) { return menuItem; } else if ( menuItem.equals( menuItemInPreview ) ) { return menuItemInPreview; } else { return menuItem; } } private int countNumberOfAccessibleChildren( Collection<MenuItemEntity> children ) { int numberOfAccessibleChildren = 0; for ( MenuItemEntity child : children ) { if ( addable( child ) ) { numberOfAccessibleChildren++; } } return numberOfAccessibleChildren; } public boolean addable( MenuItemEntity menuItem ) { return ( !menuItem.getHidden() || setting.includeHiddenMenuItems ) && access( menuItem ); } protected boolean access( MenuItemEntity menuItem ) { // Only apply access restrictions if a user exists: return ( setting.user == null ) || ( menuItemAccessResolver.hasAccess( setting.user, menuItem, MenuItemAccessType.READ ) ); } private String asEmptyIfNull( final String value ) { return value != null ? value : ""; } public void setIncludeSiteInfo( boolean includeSiteInfo ) { this.includeSiteInfo = includeSiteInfo; } public void setIncludePathInfo( boolean includePathInfo ) { this.includePathInfo = includePathInfo; } public void setIncludePageTemplateName( boolean includePageTemplateName ) { this.includePageTemplateName = includePageTemplateName; } }