* @version 0.0.0
* @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 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 {
// Load traits
use StackableTrait;
/**
* Main nodes in the XML tree ('menu' is ignored)
*/
private $mainNodes = [
'block-list',
];
/**
* Sub nodes in the XML tree
*/
private $subNodes = [
'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 = [
// List entry
'entry_id',
// Anchor
'anchor-id',
'anchor-text',
'anchor-title',
'anchor-href',
];
/**
* Variables for a menu block
*/
private $menuBlockVariables = [
// Title
'title_id',
'title_class',
'title_text',
// Content is taken from menuEntries
// Footer
'footer_id',
'footer_class',
'footer_text',
];
/**
* Rendered menu entries
*/
private $menuEntries = [];
/**
* Rendered menu blocks
*/
private $menuBlocks = [];
/**
* Current main node
*/
private $curr = [];
/**
* Content from dependency
*/
private $dependencyContent = [];
/**
* Instance of a menu
*/
private $menuInstance = NULL;
/**
* 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 = GenericRegistry::getRegistry()->getInstance('application');
// Determine base path
$templateBasePath = FrameworkBootstrap::getConfigurationInstance()->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(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('raw_template_extension'));
$templateInstance->setCodeTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_extension'));
// Absolute output path for compiled templates
$templateInstance->setCompileOutputPath(sprintf('%s%s/',
$templateBasePath,
FrameworkBootstrap::getConfigurationInstance()->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;
}
/**
* Setter for the menu instance
*
* @param $menuInstance A RenderableMenu instance
* @return void
*/
protected final function setMenuInstance (RenderableMenu $menuInstance) {
$this->menuInstance = $menuInstance;
}
/**
* Getter for the menu instance
*
* @return $menuInstance A RenderableMenu instance
*/
private final function getMenuInstance () {
return $this->menuInstance;
}
/**
* 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 (string $template) {
// Set template type
$this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->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 (string $element) {
$this->curr['main_node'] = $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, string $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' . StringUtils::convertToClassName($element);
// Set it
$this->setCurrMainNode($element);
} elseif (in_array($element, $this->getSubNodes())) {
// Sub node found
$methodName = 'start' . StringUtils::convertToClassName($element);
} elseif ($element != 'menu') {
// Invalid node name found
throw new InvalidXmlNodeException(array($this, $element, $attributes), Parseable::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, string $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()), Parseable::EXCEPTION_XML_NODE_MISMATCH);
} // END - if
// Construct method name
$methodName = 'finish' . StringUtils::convertToClassName($nodeName);
// Call the corresponding method
//* DEBUG: */ echo "call: ".$methodName."
\n";
call_user_func_array(array($this, $methodName), []);
}
/**
* 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, string $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(GenericRegistry::getRegistry()->getInstance('application'))));
// 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 () {
// Load menu entry template
$this->getTemplateInstance()->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
$this->getTemplateInstance()->assignVariable($variableName, $variableValue);
} // END - foreach
// Compile template + variables
$this->getTemplateInstance()->compileTemplate();
$this->getTemplateInstance()->compileVariables();
// Remember it here
$this->menuEntries[$this->readVariable('entry_id')] = $this->getTemplateInstance()->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);
// Load menu entry template
$this->getTemplateInstance()->loadCodeTemplate('menu_block');
// Copy all variables over to it
foreach ($this->menuBlockVariables as $variableName) {
// Copy variable
$variableValue = $this->readVariable($variableName);
// ... into the instance
$this->getTemplateInstance()->assignVariable($variableName, $variableValue);
} // END - foreach
// Assign block content
$this->getTemplateInstance()->assignVariable('block_content', $blockContent);
// Compile template + variables
$this->getTemplateInstance()->compileTemplate();
$this->getTemplateInstance()->compileVariables();
// Remember it here
array_push($this->menuBlocks, $this->getTemplateInstance()->getRawTemplateData());
// Reset rendered menu entries array
$this->menuEntries = [];
}
/**
* "Getter" for menu content
*
* @return $menuContent Returned menu content
*/
public function getMenuContent () {
// Implode menuBlocks
$menuContent = implode('', $this->menuBlocks);
// Clean variable
$this->menuBlocks = [];
// 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 = GenericRegistry::getRegistry()->getInstance('application');
// Get the file instance ready
$fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
$applicationInstance->getAppShortName(),
md5(
$this->getMenuInstance()->getMenuName() . ':' .
$this->__toString() . ':' .
$this->getMenuInstance()->__toString()
),
$this->getMenuInstance()->getMenuType()
));
// Return it
return $fileInstance;
}
}