3 namespace Org\Mxchange\CoreFramework\Template\Engine;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\InvalidDirectoryException;
9 use Org\Mxchange\CoreFramework\Menu\RenderableMenu;
10 use Org\Mxchange\CoreFramework\Parser\Parseable;
11 use Org\Mxchange\CoreFramework\Registry\GenericRegistry;
12 use Org\Mxchange\CoreFramework\Template\CompileableTemplate;
13 use Org\Mxchange\CoreFramework\Template\Engine\BaseTemplateEngine;
14 use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
18 use \UnexpectedValueException;
21 * A Menu template engine class
23 * @author Roland Haeder <webmaster@shipsimu.org>
25 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
26 * @license GNU GPL 3.0 or any newer version
27 * @link http://www.shipsimu.org
29 * This program is free software: you can redistribute it and/or modify
30 * it under the terms of the GNU General Public License as published by
31 * the Free Software Foundation, either version 3 of the License, or
32 * (at your option) any later version.
34 * This program is distributed in the hope that it will be useful,
35 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 * GNU General Public License for more details.
39 * You should have received a copy of the GNU General Public License
40 * along with this program. If not, see <http://www.gnu.org/licenses/>.
42 class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate {
44 * Main nodes in the XML tree ('menu' is ignored)
46 private $mainNodes = [
51 * Sub nodes in the XML tree
79 * Variables for a menu entry
81 private $menuEntryVariables = [
92 * Variables for a menu block
94 private $menuBlockVariables = [
99 // Content is taken from menuEntries
107 * Rendered menu entries
109 private $menuEntries = [];
112 * Rendered menu blocks
114 private $menuBlocks = [];
122 * Content from dependency
124 private $dependencyContent = [];
129 private $menuInstance = NULL;
132 * Template engine instance
134 private $templateInstance = NULL;
137 * Protected constructor
141 protected function __construct () {
142 // Call parent constructor
143 parent::__construct(__CLASS__);
147 * Creates an instance of the class TemplateEngine and prepares it for usage
149 * @param $menuInstance A RenderableMenu instance
150 * @return $templateInstance An instance of TemplateEngine
151 * @throws UnexpectedValueException If the found $templateBasePath is empty or not a string
152 * @throws InvalidDirectoryException If $templateBasePath is no directory or not found
153 * @throws BasePathReadProtectedException If $templateBasePath is
156 public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) {
157 // Get a new instance
158 $templateInstance = new MenuTemplateEngine();
160 // Get the application instance from registry
161 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
163 // Determine base path
164 $templateBasePath = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path') . $applicationInstance->getAppShortName(). '/';
166 // Is the base path valid?
167 if (empty($templateBasePath)) {
168 // Base path is empty
169 throw new UnexpectedValueException(sprintf('[%s:%d] Variable templateBasePath is empty.', $templateInstance->__toString(), __LINE__), self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
170 } elseif (!is_string($templateBasePath)) {
172 throw new UnexpectedValueException(sprintf('[%s:%d] %s is not a string with a base path.', $templateInstance->__toString(), __LINE__, $templateBasePath), self::EXCEPTION_INVALID_STRING);
173 } elseif (!is_dir($templateBasePath)) {
175 throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
176 } elseif (!is_readable($templateBasePath)) {
178 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
182 $templateInstance->setTemplateBasePath($templateBasePath);
184 // Set template extensions
185 $templateInstance->setRawTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('raw_template_extension'));
186 $templateInstance->setCodeTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_extension'));
188 // Absolute output path for compiled templates
189 $templateInstance->setCompileOutputPath(sprintf('%s%s/',
191 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('compile_output_path')
194 // Set the menu instance
195 $templateInstance->setMenuInstance($menuInstance);
197 // Init a variable stacker
198 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
201 $templateInstance->setStackInstance($stackInstance);
203 // Return the prepared instance
204 return $templateInstance;
208 * Setter for the menu instance
210 * @param $menuInstance A RenderableMenu instance
213 protected final function setMenuInstance (RenderableMenu $menuInstance) {
214 $this->menuInstance = $menuInstance;
218 * Getter for the menu instance
220 * @return $menuInstance A RenderableMenu instance
222 private final function getMenuInstance () {
223 return $this->menuInstance;
227 * Setter for template engine instances
229 * @param $templateInstance An instance of a CompileableTemplate class
232 protected final function setTemplateInstance (CompileableTemplate $templateInstance) {
233 $this->templateInstance = $templateInstance;
237 * Getter for template engine instances
239 * @return $templateInstance An instance of a CompileableTemplate class
241 public final function getTemplateInstance () {
242 return $this->templateInstance;
246 * Load a specified menu template into the engine
248 * @param $template The menu template we shall load which is
249 * located in 'menu' by default
252 public function loadMenuTemplate ($template) {
254 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_type'));
256 // Load the special template
257 $this->loadTemplate($template);
261 * Getter for current main node
263 * @return $currMainNode Current main node
265 public final function getCurrMainNode () {
266 return $this->curr['main_node'];
270 * Setter for current main node
272 * @param $element Element name to set as current main node
273 * @return $currMainNode Current main node
275 private final function setCurrMainNode ($element) {
276 $this->curr['main_node'] = (string) $element;
280 * Getter for main node array
282 * @return $mainNodes Array with valid main node names
284 public final function getMainNodes () {
285 return $this->mainNodes;
289 * Getter for sub node array
291 * @return $subNodes Array with valid sub node names
293 public final function getSubNodes () {
294 return $this->subNodes;
298 * Handles the start element of an XML resource
300 * @param $resource XML parser resource (currently ignored)
301 * @param $element The element we shall handle
302 * @param $attributes All attributes
304 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
306 public function startElement ($resource, $element, array $attributes) {
307 // Initial method name which will never be called...
308 $methodName = 'initMenu';
310 // Make the element name lower-case
311 $element = strtolower($element);
313 // Is the element a main node?
314 //* DEBUG: */ echo "START: >".$element."<<br />\n";
315 if (in_array($element, $this->getMainNodes())) {
316 // Okay, main node found!
317 $methodName = 'start' . StringUtils::convertToClassName($element);
320 $this->setCurrMainNode($element);
321 } elseif (in_array($element, $this->getSubNodes())) {
323 $methodName = 'start' . StringUtils::convertToClassName($element);
324 } elseif ($element != 'menu') {
325 // Invalid node name found
326 throw new InvalidXmlNodeException(array($this, $element, $attributes), Parseable::EXCEPTION_XML_NODE_UNKNOWN);
330 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
331 call_user_func_array(array($this, $methodName), $attributes);
335 * Ends the main or sub node by sending out the gathered data
337 * @param $resource An XML resource pointer (currently ignored)
338 * @param $nodeName Name of the node we want to finish
340 * @throws XmlNodeMismatchException If current main node mismatches the closing one
342 public function finishElement ($resource, $nodeName) {
343 // Make all lower-case
344 $nodeName = strtolower($nodeName);
346 // Does this match with current main node?
347 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
348 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
350 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), Parseable::EXCEPTION_XML_NODE_MISMATCH);
353 // Construct method name
354 $methodName = 'finish' . StringUtils::convertToClassName($nodeName);
356 // Call the corresponding method
357 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
358 call_user_func_array(array($this, $methodName), array());
364 * @param $resource XML parser resource (currently ignored)
365 * @param $characters Characters to handle
367 * @todo Find something useful with this!
369 public function characterHandler ($resource, $characters) {
370 // Trim all spaces away
371 $characters = trim($characters);
373 // Is this string empty?
374 if (empty($characters)) {
375 // Then skip it silently
379 // Assign the found characters to variable and use the last entry from
381 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
385 * Handles the template dependency for given node
387 * @param $node The node we should load a dependency template
388 * @param $templateDependency A template to load to satisfy dependencies
391 private function handleTemplateDependency ($node, $templateDependency) {
392 // Is the template dependency set?
393 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
394 // Get a temporay menu template instance
395 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
398 $templateInstance->loadMenuTemplate($templateDependency);
400 // Parse the XML content
401 $templateInstance->renderXmlContent();
403 // Save the parsed raw content in our dependency array
404 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
409 * Intializes the menu
411 * @param $templateDependency A template to load to satisfy dependencies
413 * @todo Add cache creation here
415 private function initMenu ($templateDependency = '') {
416 // Get web template engine
417 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(GenericRegistry::getRegistry()->getInstance('application'))));
419 // Handle the dependency template
420 $this->handleTemplateDependency('menu', $templateDependency);
422 // Push the node name on the stacker
423 $this->getStackInstance()->pushNamed('current_node', 'menu');
427 * Starts the menu entries
429 * @param $templateDependency A template to load to satisfy dependencies
432 private function startEntryList () {
433 // Push the node name on the stacker
434 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
438 * Starts the menu block header
442 private function startBlockHeader () {
443 // Push the node name on the stacker
444 $this->getStackInstance()->pushNamed('current_node', 'block-header');
448 * Starts the menu block footer
452 private function startBlockFooter () {
453 // Push the node name on the stacker
454 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
458 * Starts the menu property 'block-list'
462 private function startBlockList () {
463 // Push the node name on the stacker
464 $this->getStackInstance()->pushNamed('current_node', 'block-list');
468 * Starts the menu property 'block'
472 private function startBlock () {
473 // Push the node name on the stacker
474 $this->getStackInstance()->pushNamed('current_node', 'block');
478 * Starts the menu property 'title'
482 private function startTitle () {
483 // Push the node name on the stacker
484 $this->getStackInstance()->pushNamed('current_node', 'title');
488 * Starts the menu property 'title-id'
492 private function startTitleId () {
493 // Push the node name on the stacker
494 $this->getStackInstance()->pushNamed('current_node', 'title-id');
498 * Starts the menu property 'title-class'
502 private function startTitleClass () {
503 // Push the node name on the stacker
504 $this->getStackInstance()->pushNamed('current_node', 'title-class');
508 * Starts the menu property 'title-text'
512 private function startTitleText () {
513 // Push the node name on the stacker
514 $this->getStackInstance()->pushNamed('current_node', 'title-text');
518 * Starts the menu property 'entry'
522 private function startEntry () {
523 // Push the node name on the stacker
524 $this->getStackInstance()->pushNamed('current_node', 'entry');
528 * Starts the menu property 'entry-id'
532 private function startEntryId () {
533 // Push the node name on the stacker
534 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
538 * Starts the menu property 'anchor'
542 private function startAnchor () {
543 // Push the node name on the stacker
544 $this->getStackInstance()->pushNamed('current_node', 'anchor');
548 * Starts the menu property 'anchor-id'
552 private function startAnchorId () {
553 // Push the node name on the stacker
554 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
558 * Starts the menu property 'anchor-text'
562 private function startAnchorText () {
563 // Push the node name on the stacker
564 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
568 * Starts the menu property 'anchor-title'
572 private function startAnchorTitle () {
573 // Push the node name on the stacker
574 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
578 * Starts the menu property 'anchor-href'
582 private function startAnchorHref () {
583 // Push the node name on the stacker
584 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
588 * Starts the menu property 'footer-id'
592 private function startFooterId () {
593 // Push the node name on the stacker
594 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
598 * Starts the menu property 'footer-class'
602 private function startFooterClass () {
603 // Push the node name on the stacker
604 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
608 * Starts the menu property 'footer-text'
612 private function startFooterText () {
613 // Push the node name on the stacker
614 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
618 * Finishes the title node by added another template to the menu
622 private function finishTitle () {
623 // Pop the last entry
624 $this->getStackInstance()->popNamed('current_node');
628 * Finishes the title-id node by
632 private function finishTitleId () {
633 // Pop the last entry
634 $this->getStackInstance()->popNamed('current_node');
638 * Finishes the title-class node
642 private function finishTitleClass () {
643 // Pop the last entry
644 $this->getStackInstance()->popNamed('current_node');
648 * Finishes the title-class node
652 private function finishTitleText () {
653 // Pop the last entry
654 $this->getStackInstance()->popNamed('current_node');
658 * Finishes the footer-text node
662 private function finishFooterText () {
663 // Pop the last entry
664 $this->getStackInstance()->popNamed('current_node');
668 * Finishes the footer-class node
672 private function finishFooterClass () {
673 // Pop the last entry
674 $this->getStackInstance()->popNamed('current_node');
678 * Finishes the footer-id node
682 private function finishFooterId () {
683 // Pop the last entry
684 $this->getStackInstance()->popNamed('current_node');
688 * Finishes the anchor-href node
692 private function finishAnchorHref () {
693 // Pop the last entry
694 $this->getStackInstance()->popNamed('current_node');
698 * Finishes the anchor-title node
702 private function finishAnchorTitle () {
703 // Pop the last entry
704 $this->getStackInstance()->popNamed('current_node');
708 * Finishes the anchor-text node
712 private function finishAnchorText () {
713 // Pop the last entry
714 $this->getStackInstance()->popNamed('current_node');
718 * Finishes the anchor-id node
722 private function finishAnchorId () {
723 // Pop the last entry
724 $this->getStackInstance()->popNamed('current_node');
728 * Finishes the anchor node
732 private function finishAnchor () {
733 // Pop the last entry
734 $this->getStackInstance()->popNamed('current_node');
738 * Finishes the entry-id node
742 private function finishEntryId () {
743 // Pop the last entry
744 $this->getStackInstance()->popNamed('current_node');
748 * Finishes the entry node
752 private function finishEntry () {
753 // Pop the last entry
754 $this->getStackInstance()->popNamed('current_node');
756 // Render this menu entry
757 $this->renderMenuEntry();
761 * Finishes the block node
765 private function finishBlock () {
766 // Pop the last entry
767 $this->getStackInstance()->popNamed('current_node');
769 // Render this menu block
770 $this->renderMenuBlock();
774 * Finishes the block-list node
778 private function finishBlockList () {
779 // Pop the last entry
780 $this->getStackInstance()->popNamed('current_node');
784 * Finishes the menu entries
788 private function finishEntryList () {
789 // Pop the last entry
790 $this->getStackInstance()->popNamed('current_node');
794 * Finishes the menu block header
798 private function finishBlockHeader () {
799 // Pop the last entry
800 $this->getStackInstance()->popNamed('current_node');
804 * Finishes the menu block footer
808 private function finishBlockFooter () {
809 // Pop the last entry
810 $this->getStackInstance()->popNamed('current_node');
818 private function finishMenu () {
819 // Pop the last entry
820 $this->getStackInstance()->popNamed('current_node');
824 * Renders this menu entry, as every block all variables got overwritten
825 * with data from next entry.
829 private function renderMenuEntry () {
830 // Load menu entry template
831 $this->getTemplateInstance()->loadCodeTemplate('menu_entry');
833 // Copy all variables over to it
834 foreach ($this->menuEntryVariables as $variableName) {
836 $variableValue = $this->readVariable($variableName);
838 // Is the key 'anchor-href'?
839 if ($variableName == 'anchor-href') {
840 // Expand variable with URL then
841 $variableValue = '{?base_url?}/' . $variableValue;
844 // ... into the instance
845 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
848 // Compile template + variables
849 $this->getTemplateInstance()->compileTemplate();
850 $this->getTemplateInstance()->compileVariables();
853 $this->menuEntries[$this->readVariable('entry_id')] = $this->getTemplateInstance()->getRawTemplateData();
857 * Renders this menu block, as next block all data is overwritten with
862 private function renderMenuBlock () {
863 // Init block content
864 $blockContent = implode('', $this->menuEntries);
866 // Load menu entry template
867 $this->getTemplateInstance()->loadCodeTemplate('menu_block');
869 // Copy all variables over to it
870 foreach ($this->menuBlockVariables as $variableName) {
872 $variableValue = $this->readVariable($variableName);
874 // ... into the instance
875 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
878 // Assign block content
879 $this->getTemplateInstance()->assignVariable('block_content', $blockContent);
881 // Compile template + variables
882 $this->getTemplateInstance()->compileTemplate();
883 $this->getTemplateInstance()->compileVariables();
886 array_push($this->menuBlocks, $this->getTemplateInstance()->getRawTemplateData());
888 // Reset rendered menu entries array
889 $this->menuEntries = [];
893 * "Getter" for menu content
895 * @return $menuContent Returned menu content
897 public function getMenuContent () {
898 // Implode menuBlocks
899 $menuContent = implode('', $this->menuBlocks);
902 $this->menuBlocks = [];
909 * Getter for menu cache file instance
911 * @return $fileInstance Full-qualified file name of the menu cache
913 public function getMenuCacheFile () {
914 // Get the application instance from registry
915 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
917 // Get the file instance ready
918 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
919 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
920 $applicationInstance->getAppShortName(),
922 $this->getMenuInstance()->getMenuName() . ':' .
923 $this->__toString() . ':' .
924 $this->getMenuInstance()->__toString()
926 $this->getMenuInstance()->getMenuType()
930 return $fileInstance;