* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate { /** * Main nodes in the XML tree ('menu' is ignored) */ private $mainNodes = array( 'block-list', ); /** * Sub nodes in the XML tree */ private $subNodes = array( 'entry-list', 'entry', 'entry-id', 'entries-content', 'block-header', 'block-footer', 'footer-id', 'footer-class', 'footer-text', 'block', 'title', 'title-id', 'title-class', 'title-text', 'design', 'text', 'advert', 'anchor', 'anchor-id', 'anchor-text', 'anchor-title', 'anchor-href', ); /** * Variables for a menu entry */ private $menuEntryVariables = array( // List entry 'entry_id', // Anchor 'anchor-id', 'anchor-text', 'anchor-title', 'anchor-href', ); /** * Variables for a menu block */ private $menuBlockVariables = array( // Title 'title_id', 'title_class', 'title_text', // Content is taken from menuEntries // Footer 'footer_id', 'footer_class', 'footer_text', ); /** * Rendered menu entries */ private $menuEntries = array(); /** * Rendered menu blocks */ private $menuBlocks = array(); /** * Menu instance */ private $menuInstance = NULL; /** * Current main node */ private $curr = array(); /** * Content from dependency */ private $dependencyContent = array(); /** * Protected constructor * * @return void */ protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); } /** * Creates an instance of the class TemplateEngine and prepares it for usage * * @param $menuInstance A RenderableMenu instance * @return $templateInstance An instance of TemplateEngine * @throws UnexpectedValueException If the found $templateBasePath is empty or not a string * @throws InvalidDirectoryException If $templateBasePath is no directory or not found * @throws BasePathReadProtectedException If $templateBasePath is * read-protected */ public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) { // Get a new instance $templateInstance = new MenuTemplateEngine(); // Get the application instance from registry $applicationInstance = Registry::getRegistry()->getInstance('app'); // Determine base path $templateBasePath = $templateInstance->getConfigInstance()->getConfigEntry('application_base_path') . $applicationInstance->getAppShortName(). '/'; // Is the base path valid? if (empty($templateBasePath)) { // Base path is empty throw new UnexpectedValueException(sprintf('[%s:%d] Variable templateBasePath is empty.', $templateInstance->__toString(), __LINE__), self::EXCEPTION_UNEXPECTED_EMPTY_STRING); } elseif (!is_string($templateBasePath)) { // Is not a string throw new UnexpectedValueException(sprintf('[%s:%d] %s is not a string with a base path.', $templateInstance->__toString(), __LINE__, $templateBasePath), self::EXCEPTION_INVALID_STRING); } elseif (!is_dir($templateBasePath)) { // Is not a path throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME); } elseif (!is_readable($templateBasePath)) { // Is not readable throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH); } // Set the base path $templateInstance->setTemplateBasePath($templateBasePath); // Set template extensions $templateInstance->setRawTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('raw_template_extension')); $templateInstance->setCodeTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('menu_template_extension')); // Absolute output path for compiled templates $templateInstance->setCompileOutputPath(sprintf('%s%s/', $templateBasePath, $templateInstance->getConfigInstance()->getConfigEntry('compile_output_path') )); // Set the menu instance $templateInstance->setMenuInstance($menuInstance); // Init a variable stacker $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class'); // Set it $templateInstance->setStackInstance($stackInstance); // Return the prepared instance return $templateInstance; } /** * Load a specified menu template into the engine * * @param $template The menu template we shall load which is * located in 'menu' by default * @return void */ public function loadMenuTemplate ($template) { // Set template type $this->setTemplateType($this->getConfigInstance()->getConfigEntry('menu_template_type')); // Load the special template $this->loadTemplate($template); } /** * Getter for current main node * * @return $currMainNode Current main node */ public final function getCurrMainNode () { return $this->curr['main_node']; } /** * Setter for current main node * * @param $element Element name to set as current main node * @return $currMainNode Current main node */ private final function setCurrMainNode ($element) { $this->curr['main_node'] = (string) $element; } /** * Getter for main node array * * @return $mainNodes Array with valid main node names */ public final function getMainNodes () { return $this->mainNodes; } /** * Getter for sub node array * * @return $subNodes Array with valid sub node names */ public final function getSubNodes () { return $this->subNodes; } /** * Handles the start element of an XML resource * * @param $resource XML parser resource (currently ignored) * @param $element The element we shall handle * @param $attributes All attributes * @return void * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found */ public function startElement ($resource, $element, array $attributes) { // Initial method name which will never be called... $methodName = 'initMenu'; // Make the element name lower-case $element = strtolower($element); // Is the element a main node? //* DEBUG: */ echo "START: >".$element."<
\n"; if (in_array($element, $this->getMainNodes())) { // Okay, main node found! $methodName = 'start' . self::convertToClassName($element); // Set it $this->setCurrMainNode($element); } elseif (in_array($element, $this->getSubNodes())) { // Sub node found $methodName = 'start' . self::convertToClassName($element); } elseif ($element != 'menu') { // Invalid node name found throw new InvalidXmlNodeException(array($this, $element, $attributes), XmlParser::EXCEPTION_XML_NODE_UNKNOWN); } // Call method //* DEBUG: */ echo "call: ".$methodName."
\n"; call_user_func_array(array($this, $methodName), $attributes); } /** * Ends the main or sub node by sending out the gathered data * * @param $resource An XML resource pointer (currently ignored) * @param $nodeName Name of the node we want to finish * @return void * @throws XmlNodeMismatchException If current main node mismatches the closing one */ public function finishElement ($resource, $nodeName) { // Make all lower-case $nodeName = strtolower($nodeName); // Does this match with current main node? //* DEBUG: */ echo "END: >".$nodeName."<
\n"; if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) { // Did not match! throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), XmlParser::EXCEPTION_XML_NODE_MISMATCH); } // END - if // Construct method name $methodName = 'finish' . self::convertToClassName($nodeName); // Call the corresponding method //* DEBUG: */ echo "call: ".$methodName."
\n"; call_user_func_array(array($this, $methodName), array()); } /** * Currently not used * * @param $resource XML parser resource (currently ignored) * @param $characters Characters to handle * @return void * @todo Find something useful with this! */ public function characterHandler ($resource, $characters) { // Trim all spaces away $characters = trim($characters); // Is this string empty? if (empty($characters)) { // Then skip it silently return; } // END - if // Assign the found characters to variable and use the last entry from // stack as the name parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters); } /** * Handles the template dependency for given node * * @param $node The node we should load a dependency template * @param $templateDependency A template to load to satisfy dependencies * @return void */ private function handleTemplateDependency ($node, $templateDependency) { // Is the template dependency set? if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) { // Get a temporay menu template instance $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance())); // Then load it $templateInstance->loadMenuTemplate($templateDependency); // Parse the XML content $templateInstance->renderXmlContent(); // Save the parsed raw content in our dependency array $this->dependencyContent[$node] = $templateInstance->getRawTemplateData(); } // END - if } /** * Intializes the menu * * @param $templateDependency A template to load to satisfy dependencies * @return void * @todo Add cache creation here */ private function initMenu ($templateDependency = '') { // Get web template engine $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(Registry::getRegistry()->getInstance('app')))); // Handle the dependency template $this->handleTemplateDependency('menu', $templateDependency); // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'menu'); } /** * Starts the menu entries * * @param $templateDependency A template to load to satisfy dependencies * @return void */ private function startEntryList () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'entry-list'); } /** * Starts the menu block header * * @return void */ private function startBlockHeader () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'block-header'); } /** * Starts the menu block footer * * @return void */ private function startBlockFooter () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'block-footer'); } /** * Starts the menu property 'block-list' * * @return void */ private function startBlockList () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'block-list'); } /** * Starts the menu property 'block' * * @return void */ private function startBlock () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'block'); } /** * Starts the menu property 'title' * * @return void */ private function startTitle () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'title'); } /** * Starts the menu property 'title-id' * * @return void */ private function startTitleId () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'title-id'); } /** * Starts the menu property 'title-class' * * @return void */ private function startTitleClass () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'title-class'); } /** * Starts the menu property 'title-text' * * @return void */ private function startTitleText () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'title-text'); } /** * Starts the menu property 'entry' * * @return void */ private function startEntry () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'entry'); } /** * Starts the menu property 'entry-id' * * @return void */ private function startEntryId () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'entry-id'); } /** * Starts the menu property 'anchor' * * @return void */ private function startAnchor () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'anchor'); } /** * Starts the menu property 'anchor-id' * * @return void */ private function startAnchorId () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'anchor-id'); } /** * Starts the menu property 'anchor-text' * * @return void */ private function startAnchorText () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'anchor-text'); } /** * Starts the menu property 'anchor-title' * * @return void */ private function startAnchorTitle () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'anchor-title'); } /** * Starts the menu property 'anchor-href' * * @return void */ private function startAnchorHref () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'anchor-href'); } /** * Starts the menu property 'footer-id' * * @return void */ private function startFooterId () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'footer-id'); } /** * Starts the menu property 'footer-class' * * @return void */ private function startFooterClass () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'footer-class'); } /** * Starts the menu property 'footer-text' * * @return void */ private function startFooterText () { // Push the node name on the stacker $this->getStackInstance()->pushNamed('current_node', 'footer-text'); } /** * Finishes the title node by added another template to the menu * * @return void */ private function finishTitle () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the title-id node by * * @return void */ private function finishTitleId () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the title-class node * * @return void */ private function finishTitleClass () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the title-class node * * @return void */ private function finishTitleText () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the footer-text node * * @return void */ private function finishFooterText () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the footer-class node * * @return void */ private function finishFooterClass () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the footer-id node * * @return void */ private function finishFooterId () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the anchor-href node * * @return void */ private function finishAnchorHref () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the anchor-title node * * @return void */ private function finishAnchorTitle () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the anchor-text node * * @return void */ private function finishAnchorText () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the anchor-id node * * @return void */ private function finishAnchorId () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the anchor node * * @return void */ private function finishAnchor () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the entry-id node * * @return void */ private function finishEntryId () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the entry node * * @return void */ private function finishEntry () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); // Render this menu entry $this->renderMenuEntry(); } /** * Finishes the block node * * @return void */ private function finishBlock () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); // Render this menu block $this->renderMenuBlock(); } /** * Finishes the block-list node * * @return void */ private function finishBlockList () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the menu entries * * @return void */ private function finishEntryList () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the menu block header * * @return void */ private function finishBlockHeader () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the menu block footer * * @return void */ private function finishBlockFooter () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Finishes the menu * * @return void */ private function finishMenu () { // Pop the last entry $this->getStackInstance()->popNamed('current_node'); } /** * Renders this menu entry, as every block all variables got overwritten * with data from next entry. * * @return void */ private function renderMenuEntry () { // Prepare template engine $templateInstance = $this->prepareTemplateInstance(); // Load menu entry template $templateInstance->loadCodeTemplate('menu_entry'); // Copy all variables over to it foreach ($this->menuEntryVariables as $variableName) { // Copy variable $variableValue = $this->readVariable($variableName); // Is the key 'anchor-href'? if ($variableName == 'anchor-href') { // Expand variable with URL then $variableValue = '{?base_url?}/' . $variableValue; } // END - if // ... into the instance $templateInstance->assignVariable($variableName, $variableValue); } // END - foreach // Compile template + variables $templateInstance->compileTemplate(); $templateInstance->compileVariables(); // Remember it here $this->menuEntries[$this->readVariable('entry_id')] = $templateInstance->getRawTemplateData(); } /** * Renders this menu block, as next block all data is overwritten with * next block. * * @return void */ private function renderMenuBlock () { // Init block content $blockContent = implode('', $this->menuEntries); // Prepare template engine $templateInstance = $this->prepareTemplateInstance(); // Load menu entry template $templateInstance->loadCodeTemplate('menu_block'); // Copy all variables over to it foreach ($this->menuBlockVariables as $variableName) { // Copy variable $variableValue = $this->readVariable($variableName); // ... into the instance $templateInstance->assignVariable($variableName, $variableValue); } // END - foreach // Assign block content $templateInstance->assignVariable('block_content', $blockContent); // Compile template + variables $templateInstance->compileTemplate(); $templateInstance->compileVariables(); // Remember it here array_push($this->menuBlocks, $templateInstance->getRawTemplateData()); // Reset rendered menu entries array $this->menuEntries = array(); } /** * "Getter" for menu content * * @return $menuContent Returned menu content */ public function getMenuContent () { // Implode menuBlocks $menuContent = implode('', $this->menuBlocks); // Clean variable $this->menuBlocks = array(); // And return it return $menuContent; } /** * Getter for menu cache file instance * * @return $fileInstance Full-qualified file name of the menu cache */ public function getMenuCacheFile () { // Get the application instance from registry $applicationInstance = Registry::getRegistry()->getInstance('app'); // Get the file instance ready $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s', $this->getConfigInstance()->getConfigEntry('application_base_path'), $applicationInstance->getAppShortName(), md5( $this->getMenuInstance()->getMenuName() . ':' . $this->__toString() . ':' . $this->getMenuInstance()->__toString() ), $this->getMenuInstance()->getMenuType() )); // Return it return $fileInstance; } }