3 namespace Org\Mxchange\CoreFramework\Template\Engine;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\InvalidDirectoryException;
9 use Org\Mxchange\CoreFramework\Helper\Application\ApplicationHelper;
10 use Org\Mxchange\CoreFramework\Menu\RenderableMenu;
11 use Org\Mxchange\CoreFramework\Parser\Parseable;
12 use Org\Mxchange\CoreFramework\Registry\GenericRegistry;
13 use Org\Mxchange\CoreFramework\Template\Engine\BaseTemplateEngine;
14 use Org\Mxchange\CoreFramework\Template\CompileableTemplate;
15 use Org\Mxchange\CoreFramework\Traits\Stack\StackableTrait;
16 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
20 use \UnexpectedValueException;
23 * A Menu template engine class
25 * @author Roland Haeder <webmaster@shipsimu.org>
27 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
28 * @license GNU GPL 3.0 or any newer version
29 * @link http://www.shipsimu.org
31 * This program is free software: you can redistribute it and/or modify
32 * it under the terms of the GNU General Public License as published by
33 * the Free Software Foundation, either version 3 of the License, or
34 * (at your option) any later version.
36 * This program is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
41 * You should have received a copy of the GNU General Public License
42 * along with this program. If not, see <http://www.gnu.org/licenses/>.
44 class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate {
49 * Main nodes in the XML tree ('menu' is ignored)
51 private $mainNodes = [
56 * Sub nodes in the XML tree
84 * Variables for a menu entry
86 private $menuEntryVariables = [
97 * Variables for a menu block
99 private $menuBlockVariables = [
104 // Content is taken from menuEntries
112 * Rendered menu entries
114 private $menuEntries = [];
117 * Rendered menu blocks
119 private $menuBlocks = [];
127 * Content from dependency
129 private $dependencyContent = [];
134 private $menuInstance = NULL;
137 * Protected constructor
141 private 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 = ApplicationHelper::getSelfInstance();
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_dir($templateBasePath)) {
172 throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
173 } elseif (!is_readable($templateBasePath)) {
175 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
179 $templateInstance->setTemplateBasePath($templateBasePath);
181 // Set template extensions
182 $templateInstance->setRawTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('raw_template_extension'));
183 $templateInstance->setCodeTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_extension'));
185 // Absolute output path for compiled templates
186 $templateInstance->setCompileOutputPath(sprintf('%s%s/',
188 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('compile_output_path')
191 // Set the menu instance
192 $templateInstance->setMenuInstance($menuInstance);
194 // Init a variable stacker
195 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
198 $templateInstance->setStackInstance($stackInstance);
200 // Return the prepared instance
201 return $templateInstance;
205 * Setter for the menu instance
207 * @param $menuInstance A RenderableMenu instance
210 protected final function setMenuInstance (RenderableMenu $menuInstance) {
211 $this->menuInstance = $menuInstance;
215 * Getter for the menu instance
217 * @return $menuInstance A RenderableMenu instance
219 private final function getMenuInstance () {
220 return $this->menuInstance;
224 * Load a specified menu template into the engine
226 * @param $template The menu template we shall load which is
227 * located in 'menu' by default
230 public function loadMenuTemplate (string $template) {
232 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_type'));
234 // Load the special template
235 $this->loadTemplate($template);
239 * Getter for current main node
241 * @return $currMainNode Current main node
243 public final function getCurrMainNode () {
244 return $this->curr['main_node'];
248 * Setter for current main node
250 * @param $element Element name to set as current main node
251 * @return $currMainNode Current main node
253 private final function setCurrMainNode (string $element) {
254 $this->curr['main_node'] = $element;
258 * Getter for main node array
260 * @return $mainNodes Array with valid main node names
262 public final function getMainNodes () {
263 return $this->mainNodes;
267 * Getter for sub node array
269 * @return $subNodes Array with valid sub node names
271 public final function getSubNodes () {
272 return $this->subNodes;
276 * Handles the start element of an XML resource
278 * @param $resource XML parser resource (currently ignored)
279 * @param $element The element we shall handle
280 * @param $attributes All attributes
282 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
284 public function startElement ($resource, string $element, array $attributes) {
285 // Initial method name which will never be called...
286 $methodName = 'initMenu';
288 // Make the element name lower-case
289 $element = strtolower($element);
291 // Is the element a main node?
292 //* DEBUG: */ echo "START: >".$element."<<br />\n";
293 if (in_array($element, $this->getMainNodes())) {
294 // Okay, main node found!
295 $methodName = 'start' . StringUtils::convertToClassName($element);
298 $this->setCurrMainNode($element);
299 } elseif (in_array($element, $this->getSubNodes())) {
301 $methodName = 'start' . StringUtils::convertToClassName($element);
302 } elseif ($element != 'menu') {
303 // Invalid node name found
304 throw new InvalidXmlNodeException([$this, $element, $attributes], Parseable::EXCEPTION_XML_NODE_UNKNOWN);
308 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
309 call_user_func_array(array($this, $methodName), $attributes);
313 * Ends the main or sub node by sending out the gathered data
315 * @param $resource An XML resource pointer (currently ignored)
316 * @param $nodeName Name of the node we want to finish
318 * @throws XmlNodeMismatchException If current main node mismatches the closing one
320 public function finishElement ($resource, string $nodeName) {
321 // Does this match with current main node?
322 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
323 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
325 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), Parseable::EXCEPTION_XML_NODE_MISMATCH);
328 // Construct method name
329 $methodName = 'finish' . StringUtils::convertToClassName($nodeName);
331 // Call the corresponding method
332 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
333 call_user_func_array(array($this, $methodName), []);
339 * @param $resource XML parser resource (currently ignored)
340 * @param $characters Characters to handle
342 * @todo Find something useful with this!
344 public function characterHandler ($resource, string $characters) {
345 // Trim all spaces away
346 $characters = trim($characters);
348 // Is this string empty?
349 if (empty($characters)) {
350 // Then skip it silently
354 // Assign the found characters to variable and use the last entry from
356 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
360 * Handles the template dependency for given node
362 * @param $node The node we should load a dependency template
363 * @param $templateDependency A template to load to satisfy dependencies
366 private function handleTemplateDependency (string $node, string $templateDependency) {
367 // Is the template dependency set?
368 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
369 // Get a temporay menu template instance
370 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
373 $templateInstance->loadMenuTemplate($templateDependency);
375 // Parse the XML content
376 $templateInstance->renderXmlContent();
378 // Save the parsed raw content in our dependency array
379 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
384 * Intializes the menu
386 * @param $templateDependency A template to load to satisfy dependencies
388 * @todo Add cache creation here
390 private function initMenu (string $templateDependency = '') {
391 // Get web template engine
392 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(ApplicationHelper::getSelfInstance())));
394 // Handle the dependency template
395 $this->handleTemplateDependency('menu', $templateDependency);
397 // Push the node name on the stacker
398 $this->getStackInstance()->pushNamed('current_node', 'menu');
402 * Starts the menu entries
404 * @param $templateDependency A template to load to satisfy dependencies
407 private function startEntryList () {
408 // Push the node name on the stacker
409 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
413 * Starts the menu block header
417 private function startBlockHeader () {
418 // Push the node name on the stacker
419 $this->getStackInstance()->pushNamed('current_node', 'block-header');
423 * Starts the menu block footer
427 private function startBlockFooter () {
428 // Push the node name on the stacker
429 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
433 * Starts the menu property 'block-list'
437 private function startBlockList () {
438 // Push the node name on the stacker
439 $this->getStackInstance()->pushNamed('current_node', 'block-list');
443 * Starts the menu property 'block'
447 private function startBlock () {
448 // Push the node name on the stacker
449 $this->getStackInstance()->pushNamed('current_node', 'block');
453 * Starts the menu property 'title'
457 private function startTitle () {
458 // Push the node name on the stacker
459 $this->getStackInstance()->pushNamed('current_node', 'title');
463 * Starts the menu property 'title-id'
467 private function startTitleId () {
468 // Push the node name on the stacker
469 $this->getStackInstance()->pushNamed('current_node', 'title-id');
473 * Starts the menu property 'title-class'
477 private function startTitleClass () {
478 // Push the node name on the stacker
479 $this->getStackInstance()->pushNamed('current_node', 'title-class');
483 * Starts the menu property 'title-text'
487 private function startTitleText () {
488 // Push the node name on the stacker
489 $this->getStackInstance()->pushNamed('current_node', 'title-text');
493 * Starts the menu property 'entry'
497 private function startEntry () {
498 // Push the node name on the stacker
499 $this->getStackInstance()->pushNamed('current_node', 'entry');
503 * Starts the menu property 'entry-id'
507 private function startEntryId () {
508 // Push the node name on the stacker
509 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
513 * Starts the menu property 'anchor'
517 private function startAnchor () {
518 // Push the node name on the stacker
519 $this->getStackInstance()->pushNamed('current_node', 'anchor');
523 * Starts the menu property 'anchor-id'
527 private function startAnchorId () {
528 // Push the node name on the stacker
529 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
533 * Starts the menu property 'anchor-text'
537 private function startAnchorText () {
538 // Push the node name on the stacker
539 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
543 * Starts the menu property 'anchor-title'
547 private function startAnchorTitle () {
548 // Push the node name on the stacker
549 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
553 * Starts the menu property 'anchor-href'
557 private function startAnchorHref () {
558 // Push the node name on the stacker
559 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
563 * Starts the menu property 'footer-id'
567 private function startFooterId () {
568 // Push the node name on the stacker
569 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
573 * Starts the menu property 'footer-class'
577 private function startFooterClass () {
578 // Push the node name on the stacker
579 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
583 * Starts the menu property 'footer-text'
587 private function startFooterText () {
588 // Push the node name on the stacker
589 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
593 * Finishes the title node by added another template to the menu
597 private function finishTitle () {
598 // Pop the last entry
599 $this->getStackInstance()->popNamed('current_node');
603 * Finishes the title-id node by
607 private function finishTitleId () {
608 // Pop the last entry
609 $this->getStackInstance()->popNamed('current_node');
613 * Finishes the title-class node
617 private function finishTitleClass () {
618 // Pop the last entry
619 $this->getStackInstance()->popNamed('current_node');
623 * Finishes the title-class node
627 private function finishTitleText () {
628 // Pop the last entry
629 $this->getStackInstance()->popNamed('current_node');
633 * Finishes the footer-text node
637 private function finishFooterText () {
638 // Pop the last entry
639 $this->getStackInstance()->popNamed('current_node');
643 * Finishes the footer-class node
647 private function finishFooterClass () {
648 // Pop the last entry
649 $this->getStackInstance()->popNamed('current_node');
653 * Finishes the footer-id node
657 private function finishFooterId () {
658 // Pop the last entry
659 $this->getStackInstance()->popNamed('current_node');
663 * Finishes the anchor-href node
667 private function finishAnchorHref () {
668 // Pop the last entry
669 $this->getStackInstance()->popNamed('current_node');
673 * Finishes the anchor-title node
677 private function finishAnchorTitle () {
678 // Pop the last entry
679 $this->getStackInstance()->popNamed('current_node');
683 * Finishes the anchor-text node
687 private function finishAnchorText () {
688 // Pop the last entry
689 $this->getStackInstance()->popNamed('current_node');
693 * Finishes the anchor-id node
697 private function finishAnchorId () {
698 // Pop the last entry
699 $this->getStackInstance()->popNamed('current_node');
703 * Finishes the anchor node
707 private function finishAnchor () {
708 // Pop the last entry
709 $this->getStackInstance()->popNamed('current_node');
713 * Finishes the entry-id node
717 private function finishEntryId () {
718 // Pop the last entry
719 $this->getStackInstance()->popNamed('current_node');
723 * Finishes the entry node
727 private function finishEntry () {
728 // Pop the last entry
729 $this->getStackInstance()->popNamed('current_node');
731 // Render this menu entry
732 $this->renderMenuEntry();
736 * Finishes the block node
740 private function finishBlock () {
741 // Pop the last entry
742 $this->getStackInstance()->popNamed('current_node');
744 // Render this menu block
745 $this->renderMenuBlock();
749 * Finishes the block-list node
753 private function finishBlockList () {
754 // Pop the last entry
755 $this->getStackInstance()->popNamed('current_node');
759 * Finishes the menu entries
763 private function finishEntryList () {
764 // Pop the last entry
765 $this->getStackInstance()->popNamed('current_node');
769 * Finishes the menu block header
773 private function finishBlockHeader () {
774 // Pop the last entry
775 $this->getStackInstance()->popNamed('current_node');
779 * Finishes the menu block footer
783 private function finishBlockFooter () {
784 // Pop the last entry
785 $this->getStackInstance()->popNamed('current_node');
793 private function finishMenu () {
794 // Pop the last entry
795 $this->getStackInstance()->popNamed('current_node');
799 * Renders this menu entry, as every block all variables got overwritten
800 * with data from next entry.
804 private function renderMenuEntry () {
805 // Load menu entry template
806 $this->getTemplateInstance()->loadCodeTemplate('menu_entry');
808 // Copy all variables over to it
809 foreach ($this->menuEntryVariables as $variableName) {
811 $variableValue = $this->readVariable($variableName);
813 // Is the key 'anchor-href'?
814 if ($variableName == 'anchor-href') {
815 // Expand variable with URL then
816 $variableValue = '{?base_url?}/' . $variableValue;
819 // ... into the instance
820 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
823 // Compile template + variables
824 $this->getTemplateInstance()->compileTemplate();
825 $this->getTemplateInstance()->compileVariables();
828 $this->menuEntries[$this->readVariable('entry_id')] = $this->getTemplateInstance()->getRawTemplateData();
832 * Renders this menu block, as next block all data is overwritten with
837 private function renderMenuBlock () {
838 // Init block content
839 $blockContent = implode('', $this->menuEntries);
841 // Load menu entry template
842 $this->getTemplateInstance()->loadCodeTemplate('menu_block');
844 // Copy all variables over to it
845 foreach ($this->menuBlockVariables as $variableName) {
847 $variableValue = $this->readVariable($variableName);
849 // ... into the instance
850 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
853 // Assign block content
854 $this->getTemplateInstance()->assignVariable('block_content', $blockContent);
856 // Compile template + variables
857 $this->getTemplateInstance()->compileTemplate();
858 $this->getTemplateInstance()->compileVariables();
861 array_push($this->menuBlocks, $this->getTemplateInstance()->getRawTemplateData());
863 // Reset rendered menu entries array
864 $this->menuEntries = [];
868 * "Getter" for menu content
870 * @return $menuContent Returned menu content
872 public function getMenuContent () {
873 // Implode menuBlocks
874 $menuContent = implode('', $this->menuBlocks);
877 $this->menuBlocks = [];
884 * Getter for menu cache file instance
886 * @return $fileInstance Full-qualified file name of the menu cache
888 public function getMenuCacheFile () {
889 // Get the application instance from registry
890 $applicationInstance = ApplicationHelper::getSelfInstance();
892 // Get the file instance ready
893 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
894 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
895 $applicationInstance->getAppShortName(),
897 $this->getMenuInstance()->getMenuName() . ':' .
898 $this->__toString() . ':' .
899 $this->getMenuInstance()->__toString()
901 $this->getMenuInstance()->getMenuType()
905 return $fileInstance;