3 namespace Org\Mxchange\CoreFramework\Template\Engine;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\ObjectFactory;
7 use Org\Mxchange\CoreFramework\Filesystem\InvalidDirectoryException;
8 use Org\Mxchange\CoreFramework\Menu\RenderableMenu;
9 use Org\Mxchange\CoreFramework\Parser\Xml\XmlParser;
10 use Org\Mxchange\CoreFramework\Registry\GenericRegistry;
11 use Org\Mxchange\CoreFramework\Template\CompileableTemplate;
12 use Org\Mxchange\CoreFramework\Template\Engine\BaseTemplateEngine;
16 use \UnexpectedValueException;
19 * A Menu template engine class
21 * @author Roland Haeder <webmaster@shipsimu.org>
23 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
24 * @license GNU GPL 3.0 or any newer version
25 * @link http://www.shipsimu.org
27 * This program is free software: you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation, either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program. If not, see <http://www.gnu.org/licenses/>.
40 class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate {
42 * Main nodes in the XML tree ('menu' is ignored)
44 private $mainNodes = array(
49 * Sub nodes in the XML tree
51 private $subNodes = array(
77 * Variables for a menu entry
79 private $menuEntryVariables = array(
90 * Variables for a menu block
92 private $menuBlockVariables = array(
97 // Content is taken from menuEntries
105 * Rendered menu entries
107 private $menuEntries = array();
110 * Rendered menu blocks
112 private $menuBlocks = array();
117 private $menuInstance = NULL;
122 private $curr = array();
125 * Content from dependency
127 private $dependencyContent = array();
130 * Protected constructor
134 protected function __construct () {
135 // Call parent constructor
136 parent::__construct(__CLASS__);
140 * Creates an instance of the class TemplateEngine and prepares it for usage
142 * @param $menuInstance A RenderableMenu instance
143 * @return $templateInstance An instance of TemplateEngine
144 * @throws UnexpectedValueException If the found $templateBasePath is empty or not a string
145 * @throws InvalidDirectoryException If $templateBasePath is no directory or not found
146 * @throws BasePathReadProtectedException If $templateBasePath is
149 public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) {
150 // Get a new instance
151 $templateInstance = new MenuTemplateEngine();
153 // Get the application instance from registry
154 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
156 // Determine base path
157 $templateBasePath = $templateInstance->getConfigInstance()->getConfigEntry('application_base_path') . $applicationInstance->getAppShortName(). '/';
159 // Is the base path valid?
160 if (empty($templateBasePath)) {
161 // Base path is empty
162 throw new UnexpectedValueException(sprintf('[%s:%d] Variable templateBasePath is empty.', $templateInstance->__toString(), __LINE__), self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
163 } elseif (!is_string($templateBasePath)) {
165 throw new UnexpectedValueException(sprintf('[%s:%d] %s is not a string with a base path.', $templateInstance->__toString(), __LINE__, $templateBasePath), self::EXCEPTION_INVALID_STRING);
166 } elseif (!is_dir($templateBasePath)) {
168 throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
169 } elseif (!is_readable($templateBasePath)) {
171 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
175 $templateInstance->setTemplateBasePath($templateBasePath);
177 // Set template extensions
178 $templateInstance->setRawTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('raw_template_extension'));
179 $templateInstance->setCodeTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('menu_template_extension'));
181 // Absolute output path for compiled templates
182 $templateInstance->setCompileOutputPath(sprintf('%s%s/',
184 $templateInstance->getConfigInstance()->getConfigEntry('compile_output_path')
187 // Set the menu instance
188 $templateInstance->setMenuInstance($menuInstance);
190 // Init a variable stacker
191 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
194 $templateInstance->setStackInstance($stackInstance);
196 // Return the prepared instance
197 return $templateInstance;
201 * Load a specified menu template into the engine
203 * @param $template The menu template we shall load which is
204 * located in 'menu' by default
207 public function loadMenuTemplate ($template) {
209 $this->setTemplateType($this->getConfigInstance()->getConfigEntry('menu_template_type'));
211 // Load the special template
212 $this->loadTemplate($template);
216 * Getter for current main node
218 * @return $currMainNode Current main node
220 public final function getCurrMainNode () {
221 return $this->curr['main_node'];
225 * Setter for current main node
227 * @param $element Element name to set as current main node
228 * @return $currMainNode Current main node
230 private final function setCurrMainNode ($element) {
231 $this->curr['main_node'] = (string) $element;
235 * Getter for main node array
237 * @return $mainNodes Array with valid main node names
239 public final function getMainNodes () {
240 return $this->mainNodes;
244 * Getter for sub node array
246 * @return $subNodes Array with valid sub node names
248 public final function getSubNodes () {
249 return $this->subNodes;
253 * Handles the start element of an XML resource
255 * @param $resource XML parser resource (currently ignored)
256 * @param $element The element we shall handle
257 * @param $attributes All attributes
259 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
261 public function startElement ($resource, $element, array $attributes) {
262 // Initial method name which will never be called...
263 $methodName = 'initMenu';
265 // Make the element name lower-case
266 $element = strtolower($element);
268 // Is the element a main node?
269 //* DEBUG: */ echo "START: >".$element."<<br />\n";
270 if (in_array($element, $this->getMainNodes())) {
271 // Okay, main node found!
272 $methodName = 'start' . self::convertToClassName($element);
275 $this->setCurrMainNode($element);
276 } elseif (in_array($element, $this->getSubNodes())) {
278 $methodName = 'start' . self::convertToClassName($element);
279 } elseif ($element != 'menu') {
280 // Invalid node name found
281 throw new InvalidXmlNodeException(array($this, $element, $attributes), XmlParser::EXCEPTION_XML_NODE_UNKNOWN);
285 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
286 call_user_func_array(array($this, $methodName), $attributes);
290 * Ends the main or sub node by sending out the gathered data
292 * @param $resource An XML resource pointer (currently ignored)
293 * @param $nodeName Name of the node we want to finish
295 * @throws XmlNodeMismatchException If current main node mismatches the closing one
297 public function finishElement ($resource, $nodeName) {
298 // Make all lower-case
299 $nodeName = strtolower($nodeName);
301 // Does this match with current main node?
302 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
303 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
305 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), XmlParser::EXCEPTION_XML_NODE_MISMATCH);
308 // Construct method name
309 $methodName = 'finish' . self::convertToClassName($nodeName);
311 // Call the corresponding method
312 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
313 call_user_func_array(array($this, $methodName), array());
319 * @param $resource XML parser resource (currently ignored)
320 * @param $characters Characters to handle
322 * @todo Find something useful with this!
324 public function characterHandler ($resource, $characters) {
325 // Trim all spaces away
326 $characters = trim($characters);
328 // Is this string empty?
329 if (empty($characters)) {
330 // Then skip it silently
334 // Assign the found characters to variable and use the last entry from
336 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
340 * Handles the template dependency for given node
342 * @param $node The node we should load a dependency template
343 * @param $templateDependency A template to load to satisfy dependencies
346 private function handleTemplateDependency ($node, $templateDependency) {
347 // Is the template dependency set?
348 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
349 // Get a temporay menu template instance
350 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
353 $templateInstance->loadMenuTemplate($templateDependency);
355 // Parse the XML content
356 $templateInstance->renderXmlContent();
358 // Save the parsed raw content in our dependency array
359 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
364 * Intializes the menu
366 * @param $templateDependency A template to load to satisfy dependencies
368 * @todo Add cache creation here
370 private function initMenu ($templateDependency = '') {
371 // Get web template engine
372 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(GenericRegistry::getRegistry()->getInstance('application'))));
374 // Handle the dependency template
375 $this->handleTemplateDependency('menu', $templateDependency);
377 // Push the node name on the stacker
378 $this->getStackInstance()->pushNamed('current_node', 'menu');
382 * Starts the menu entries
384 * @param $templateDependency A template to load to satisfy dependencies
387 private function startEntryList () {
388 // Push the node name on the stacker
389 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
393 * Starts the menu block header
397 private function startBlockHeader () {
398 // Push the node name on the stacker
399 $this->getStackInstance()->pushNamed('current_node', 'block-header');
403 * Starts the menu block footer
407 private function startBlockFooter () {
408 // Push the node name on the stacker
409 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
413 * Starts the menu property 'block-list'
417 private function startBlockList () {
418 // Push the node name on the stacker
419 $this->getStackInstance()->pushNamed('current_node', 'block-list');
423 * Starts the menu property 'block'
427 private function startBlock () {
428 // Push the node name on the stacker
429 $this->getStackInstance()->pushNamed('current_node', 'block');
433 * Starts the menu property 'title'
437 private function startTitle () {
438 // Push the node name on the stacker
439 $this->getStackInstance()->pushNamed('current_node', 'title');
443 * Starts the menu property 'title-id'
447 private function startTitleId () {
448 // Push the node name on the stacker
449 $this->getStackInstance()->pushNamed('current_node', 'title-id');
453 * Starts the menu property 'title-class'
457 private function startTitleClass () {
458 // Push the node name on the stacker
459 $this->getStackInstance()->pushNamed('current_node', 'title-class');
463 * Starts the menu property 'title-text'
467 private function startTitleText () {
468 // Push the node name on the stacker
469 $this->getStackInstance()->pushNamed('current_node', 'title-text');
473 * Starts the menu property 'entry'
477 private function startEntry () {
478 // Push the node name on the stacker
479 $this->getStackInstance()->pushNamed('current_node', 'entry');
483 * Starts the menu property 'entry-id'
487 private function startEntryId () {
488 // Push the node name on the stacker
489 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
493 * Starts the menu property 'anchor'
497 private function startAnchor () {
498 // Push the node name on the stacker
499 $this->getStackInstance()->pushNamed('current_node', 'anchor');
503 * Starts the menu property 'anchor-id'
507 private function startAnchorId () {
508 // Push the node name on the stacker
509 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
513 * Starts the menu property 'anchor-text'
517 private function startAnchorText () {
518 // Push the node name on the stacker
519 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
523 * Starts the menu property 'anchor-title'
527 private function startAnchorTitle () {
528 // Push the node name on the stacker
529 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
533 * Starts the menu property 'anchor-href'
537 private function startAnchorHref () {
538 // Push the node name on the stacker
539 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
543 * Starts the menu property 'footer-id'
547 private function startFooterId () {
548 // Push the node name on the stacker
549 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
553 * Starts the menu property 'footer-class'
557 private function startFooterClass () {
558 // Push the node name on the stacker
559 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
563 * Starts the menu property 'footer-text'
567 private function startFooterText () {
568 // Push the node name on the stacker
569 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
573 * Finishes the title node by added another template to the menu
577 private function finishTitle () {
578 // Pop the last entry
579 $this->getStackInstance()->popNamed('current_node');
583 * Finishes the title-id node by
587 private function finishTitleId () {
588 // Pop the last entry
589 $this->getStackInstance()->popNamed('current_node');
593 * Finishes the title-class node
597 private function finishTitleClass () {
598 // Pop the last entry
599 $this->getStackInstance()->popNamed('current_node');
603 * Finishes the title-class node
607 private function finishTitleText () {
608 // Pop the last entry
609 $this->getStackInstance()->popNamed('current_node');
613 * Finishes the footer-text node
617 private function finishFooterText () {
618 // Pop the last entry
619 $this->getStackInstance()->popNamed('current_node');
623 * Finishes the footer-class node
627 private function finishFooterClass () {
628 // Pop the last entry
629 $this->getStackInstance()->popNamed('current_node');
633 * Finishes the footer-id node
637 private function finishFooterId () {
638 // Pop the last entry
639 $this->getStackInstance()->popNamed('current_node');
643 * Finishes the anchor-href node
647 private function finishAnchorHref () {
648 // Pop the last entry
649 $this->getStackInstance()->popNamed('current_node');
653 * Finishes the anchor-title node
657 private function finishAnchorTitle () {
658 // Pop the last entry
659 $this->getStackInstance()->popNamed('current_node');
663 * Finishes the anchor-text node
667 private function finishAnchorText () {
668 // Pop the last entry
669 $this->getStackInstance()->popNamed('current_node');
673 * Finishes the anchor-id node
677 private function finishAnchorId () {
678 // Pop the last entry
679 $this->getStackInstance()->popNamed('current_node');
683 * Finishes the anchor node
687 private function finishAnchor () {
688 // Pop the last entry
689 $this->getStackInstance()->popNamed('current_node');
693 * Finishes the entry-id node
697 private function finishEntryId () {
698 // Pop the last entry
699 $this->getStackInstance()->popNamed('current_node');
703 * Finishes the entry node
707 private function finishEntry () {
708 // Pop the last entry
709 $this->getStackInstance()->popNamed('current_node');
711 // Render this menu entry
712 $this->renderMenuEntry();
716 * Finishes the block node
720 private function finishBlock () {
721 // Pop the last entry
722 $this->getStackInstance()->popNamed('current_node');
724 // Render this menu block
725 $this->renderMenuBlock();
729 * Finishes the block-list node
733 private function finishBlockList () {
734 // Pop the last entry
735 $this->getStackInstance()->popNamed('current_node');
739 * Finishes the menu entries
743 private function finishEntryList () {
744 // Pop the last entry
745 $this->getStackInstance()->popNamed('current_node');
749 * Finishes the menu block header
753 private function finishBlockHeader () {
754 // Pop the last entry
755 $this->getStackInstance()->popNamed('current_node');
759 * Finishes the menu block footer
763 private function finishBlockFooter () {
764 // Pop the last entry
765 $this->getStackInstance()->popNamed('current_node');
773 private function finishMenu () {
774 // Pop the last entry
775 $this->getStackInstance()->popNamed('current_node');
779 * Renders this menu entry, as every block all variables got overwritten
780 * with data from next entry.
784 private function renderMenuEntry () {
785 // Prepare template engine
786 $templateInstance = $this->prepareTemplateInstance();
788 // Load menu entry template
789 $templateInstance->loadCodeTemplate('menu_entry');
791 // Copy all variables over to it
792 foreach ($this->menuEntryVariables as $variableName) {
794 $variableValue = $this->readVariable($variableName);
796 // Is the key 'anchor-href'?
797 if ($variableName == 'anchor-href') {
798 // Expand variable with URL then
799 $variableValue = '{?base_url?}/' . $variableValue;
802 // ... into the instance
803 $templateInstance->assignVariable($variableName, $variableValue);
806 // Compile template + variables
807 $templateInstance->compileTemplate();
808 $templateInstance->compileVariables();
811 $this->menuEntries[$this->readVariable('entry_id')] = $templateInstance->getRawTemplateData();
815 * Renders this menu block, as next block all data is overwritten with
820 private function renderMenuBlock () {
821 // Init block content
822 $blockContent = implode('', $this->menuEntries);
824 // Prepare template engine
825 $templateInstance = $this->prepareTemplateInstance();
827 // Load menu entry template
828 $templateInstance->loadCodeTemplate('menu_block');
830 // Copy all variables over to it
831 foreach ($this->menuBlockVariables as $variableName) {
833 $variableValue = $this->readVariable($variableName);
835 // ... into the instance
836 $templateInstance->assignVariable($variableName, $variableValue);
839 // Assign block content
840 $templateInstance->assignVariable('block_content', $blockContent);
842 // Compile template + variables
843 $templateInstance->compileTemplate();
844 $templateInstance->compileVariables();
847 array_push($this->menuBlocks, $templateInstance->getRawTemplateData());
849 // Reset rendered menu entries array
850 $this->menuEntries = array();
854 * "Getter" for menu content
856 * @return $menuContent Returned menu content
858 public function getMenuContent () {
859 // Implode menuBlocks
860 $menuContent = implode('', $this->menuBlocks);
863 $this->menuBlocks = array();
870 * Getter for menu cache file instance
872 * @return $fileInstance Full-qualified file name of the menu cache
874 public function getMenuCacheFile () {
875 // Get the application instance from registry
876 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
878 // Get the file instance ready
879 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
880 $this->getConfigInstance()->getConfigEntry('application_base_path'),
881 $applicationInstance->getAppShortName(),
883 $this->getMenuInstance()->getMenuName() . ':' .
884 $this->__toString() . ':' .
885 $this->getMenuInstance()->__toString()
887 $this->getMenuInstance()->getMenuType()
891 return $fileInstance;