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\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\String\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 - 2020 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 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 = 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_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 * Load a specified menu template into the engine
229 * @param $template The menu template we shall load which is
230 * located in 'menu' by default
233 public function loadMenuTemplate (string $template) {
235 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_type'));
237 // Load the special template
238 $this->loadTemplate($template);
242 * Getter for current main node
244 * @return $currMainNode Current main node
246 public final function getCurrMainNode () {
247 return $this->curr['main_node'];
251 * Setter for current main node
253 * @param $element Element name to set as current main node
254 * @return $currMainNode Current main node
256 private final function setCurrMainNode (string $element) {
257 $this->curr['main_node'] = $element;
261 * Getter for main node array
263 * @return $mainNodes Array with valid main node names
265 public final function getMainNodes () {
266 return $this->mainNodes;
270 * Getter for sub node array
272 * @return $subNodes Array with valid sub node names
274 public final function getSubNodes () {
275 return $this->subNodes;
279 * Handles the start element of an XML resource
281 * @param $resource XML parser resource (currently ignored)
282 * @param $element The element we shall handle
283 * @param $attributes All attributes
285 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
287 public function startElement ($resource, string $element, array $attributes) {
288 // Initial method name which will never be called...
289 $methodName = 'initMenu';
291 // Make the element name lower-case
292 $element = strtolower($element);
294 // Is the element a main node?
295 //* DEBUG: */ echo "START: >".$element."<<br />\n";
296 if (in_array($element, $this->getMainNodes())) {
297 // Okay, main node found!
298 $methodName = 'start' . StringUtils::convertToClassName($element);
301 $this->setCurrMainNode($element);
302 } elseif (in_array($element, $this->getSubNodes())) {
304 $methodName = 'start' . StringUtils::convertToClassName($element);
305 } elseif ($element != 'menu') {
306 // Invalid node name found
307 throw new InvalidXmlNodeException(array($this, $element, $attributes), Parseable::EXCEPTION_XML_NODE_UNKNOWN);
311 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
312 call_user_func_array(array($this, $methodName), $attributes);
316 * Ends the main or sub node by sending out the gathered data
318 * @param $resource An XML resource pointer (currently ignored)
319 * @param $nodeName Name of the node we want to finish
321 * @throws XmlNodeMismatchException If current main node mismatches the closing one
323 public function finishElement ($resource, string $nodeName) {
324 // Does this match with current main node?
325 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
326 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
328 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), Parseable::EXCEPTION_XML_NODE_MISMATCH);
331 // Construct method name
332 $methodName = 'finish' . StringUtils::convertToClassName($nodeName);
334 // Call the corresponding method
335 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
336 call_user_func_array(array($this, $methodName), []);
342 * @param $resource XML parser resource (currently ignored)
343 * @param $characters Characters to handle
345 * @todo Find something useful with this!
347 public function characterHandler ($resource, string $characters) {
348 // Trim all spaces away
349 $characters = trim($characters);
351 // Is this string empty?
352 if (empty($characters)) {
353 // Then skip it silently
357 // Assign the found characters to variable and use the last entry from
359 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
363 * Handles the template dependency for given node
365 * @param $node The node we should load a dependency template
366 * @param $templateDependency A template to load to satisfy dependencies
369 private function handleTemplateDependency (string $node, string $templateDependency) {
370 // Is the template dependency set?
371 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
372 // Get a temporay menu template instance
373 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
376 $templateInstance->loadMenuTemplate($templateDependency);
378 // Parse the XML content
379 $templateInstance->renderXmlContent();
381 // Save the parsed raw content in our dependency array
382 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
387 * Intializes the menu
389 * @param $templateDependency A template to load to satisfy dependencies
391 * @todo Add cache creation here
393 private function initMenu (string $templateDependency = '') {
394 // Get web template engine
395 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(ApplicationHelper::getSelfInstance())));
397 // Handle the dependency template
398 $this->handleTemplateDependency('menu', $templateDependency);
400 // Push the node name on the stacker
401 $this->getStackInstance()->pushNamed('current_node', 'menu');
405 * Starts the menu entries
407 * @param $templateDependency A template to load to satisfy dependencies
410 private function startEntryList () {
411 // Push the node name on the stacker
412 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
416 * Starts the menu block header
420 private function startBlockHeader () {
421 // Push the node name on the stacker
422 $this->getStackInstance()->pushNamed('current_node', 'block-header');
426 * Starts the menu block footer
430 private function startBlockFooter () {
431 // Push the node name on the stacker
432 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
436 * Starts the menu property 'block-list'
440 private function startBlockList () {
441 // Push the node name on the stacker
442 $this->getStackInstance()->pushNamed('current_node', 'block-list');
446 * Starts the menu property 'block'
450 private function startBlock () {
451 // Push the node name on the stacker
452 $this->getStackInstance()->pushNamed('current_node', 'block');
456 * Starts the menu property 'title'
460 private function startTitle () {
461 // Push the node name on the stacker
462 $this->getStackInstance()->pushNamed('current_node', 'title');
466 * Starts the menu property 'title-id'
470 private function startTitleId () {
471 // Push the node name on the stacker
472 $this->getStackInstance()->pushNamed('current_node', 'title-id');
476 * Starts the menu property 'title-class'
480 private function startTitleClass () {
481 // Push the node name on the stacker
482 $this->getStackInstance()->pushNamed('current_node', 'title-class');
486 * Starts the menu property 'title-text'
490 private function startTitleText () {
491 // Push the node name on the stacker
492 $this->getStackInstance()->pushNamed('current_node', 'title-text');
496 * Starts the menu property 'entry'
500 private function startEntry () {
501 // Push the node name on the stacker
502 $this->getStackInstance()->pushNamed('current_node', 'entry');
506 * Starts the menu property 'entry-id'
510 private function startEntryId () {
511 // Push the node name on the stacker
512 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
516 * Starts the menu property 'anchor'
520 private function startAnchor () {
521 // Push the node name on the stacker
522 $this->getStackInstance()->pushNamed('current_node', 'anchor');
526 * Starts the menu property 'anchor-id'
530 private function startAnchorId () {
531 // Push the node name on the stacker
532 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
536 * Starts the menu property 'anchor-text'
540 private function startAnchorText () {
541 // Push the node name on the stacker
542 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
546 * Starts the menu property 'anchor-title'
550 private function startAnchorTitle () {
551 // Push the node name on the stacker
552 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
556 * Starts the menu property 'anchor-href'
560 private function startAnchorHref () {
561 // Push the node name on the stacker
562 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
566 * Starts the menu property 'footer-id'
570 private function startFooterId () {
571 // Push the node name on the stacker
572 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
576 * Starts the menu property 'footer-class'
580 private function startFooterClass () {
581 // Push the node name on the stacker
582 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
586 * Starts the menu property 'footer-text'
590 private function startFooterText () {
591 // Push the node name on the stacker
592 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
596 * Finishes the title node by added another template to the menu
600 private function finishTitle () {
601 // Pop the last entry
602 $this->getStackInstance()->popNamed('current_node');
606 * Finishes the title-id node by
610 private function finishTitleId () {
611 // Pop the last entry
612 $this->getStackInstance()->popNamed('current_node');
616 * Finishes the title-class node
620 private function finishTitleClass () {
621 // Pop the last entry
622 $this->getStackInstance()->popNamed('current_node');
626 * Finishes the title-class node
630 private function finishTitleText () {
631 // Pop the last entry
632 $this->getStackInstance()->popNamed('current_node');
636 * Finishes the footer-text node
640 private function finishFooterText () {
641 // Pop the last entry
642 $this->getStackInstance()->popNamed('current_node');
646 * Finishes the footer-class node
650 private function finishFooterClass () {
651 // Pop the last entry
652 $this->getStackInstance()->popNamed('current_node');
656 * Finishes the footer-id node
660 private function finishFooterId () {
661 // Pop the last entry
662 $this->getStackInstance()->popNamed('current_node');
666 * Finishes the anchor-href node
670 private function finishAnchorHref () {
671 // Pop the last entry
672 $this->getStackInstance()->popNamed('current_node');
676 * Finishes the anchor-title node
680 private function finishAnchorTitle () {
681 // Pop the last entry
682 $this->getStackInstance()->popNamed('current_node');
686 * Finishes the anchor-text node
690 private function finishAnchorText () {
691 // Pop the last entry
692 $this->getStackInstance()->popNamed('current_node');
696 * Finishes the anchor-id node
700 private function finishAnchorId () {
701 // Pop the last entry
702 $this->getStackInstance()->popNamed('current_node');
706 * Finishes the anchor node
710 private function finishAnchor () {
711 // Pop the last entry
712 $this->getStackInstance()->popNamed('current_node');
716 * Finishes the entry-id node
720 private function finishEntryId () {
721 // Pop the last entry
722 $this->getStackInstance()->popNamed('current_node');
726 * Finishes the entry node
730 private function finishEntry () {
731 // Pop the last entry
732 $this->getStackInstance()->popNamed('current_node');
734 // Render this menu entry
735 $this->renderMenuEntry();
739 * Finishes the block node
743 private function finishBlock () {
744 // Pop the last entry
745 $this->getStackInstance()->popNamed('current_node');
747 // Render this menu block
748 $this->renderMenuBlock();
752 * Finishes the block-list node
756 private function finishBlockList () {
757 // Pop the last entry
758 $this->getStackInstance()->popNamed('current_node');
762 * Finishes the menu entries
766 private function finishEntryList () {
767 // Pop the last entry
768 $this->getStackInstance()->popNamed('current_node');
772 * Finishes the menu block header
776 private function finishBlockHeader () {
777 // Pop the last entry
778 $this->getStackInstance()->popNamed('current_node');
782 * Finishes the menu block footer
786 private function finishBlockFooter () {
787 // Pop the last entry
788 $this->getStackInstance()->popNamed('current_node');
796 private function finishMenu () {
797 // Pop the last entry
798 $this->getStackInstance()->popNamed('current_node');
802 * Renders this menu entry, as every block all variables got overwritten
803 * with data from next entry.
807 private function renderMenuEntry () {
808 // Load menu entry template
809 $this->getTemplateInstance()->loadCodeTemplate('menu_entry');
811 // Copy all variables over to it
812 foreach ($this->menuEntryVariables as $variableName) {
814 $variableValue = $this->readVariable($variableName);
816 // Is the key 'anchor-href'?
817 if ($variableName == 'anchor-href') {
818 // Expand variable with URL then
819 $variableValue = '{?base_url?}/' . $variableValue;
822 // ... into the instance
823 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
826 // Compile template + variables
827 $this->getTemplateInstance()->compileTemplate();
828 $this->getTemplateInstance()->compileVariables();
831 $this->menuEntries[$this->readVariable('entry_id')] = $this->getTemplateInstance()->getRawTemplateData();
835 * Renders this menu block, as next block all data is overwritten with
840 private function renderMenuBlock () {
841 // Init block content
842 $blockContent = implode('', $this->menuEntries);
844 // Load menu entry template
845 $this->getTemplateInstance()->loadCodeTemplate('menu_block');
847 // Copy all variables over to it
848 foreach ($this->menuBlockVariables as $variableName) {
850 $variableValue = $this->readVariable($variableName);
852 // ... into the instance
853 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
856 // Assign block content
857 $this->getTemplateInstance()->assignVariable('block_content', $blockContent);
859 // Compile template + variables
860 $this->getTemplateInstance()->compileTemplate();
861 $this->getTemplateInstance()->compileVariables();
864 array_push($this->menuBlocks, $this->getTemplateInstance()->getRawTemplateData());
866 // Reset rendered menu entries array
867 $this->menuEntries = [];
871 * "Getter" for menu content
873 * @return $menuContent Returned menu content
875 public function getMenuContent () {
876 // Implode menuBlocks
877 $menuContent = implode('', $this->menuBlocks);
880 $this->menuBlocks = [];
887 * Getter for menu cache file instance
889 * @return $fileInstance Full-qualified file name of the menu cache
891 public function getMenuCacheFile () {
892 // Get the application instance from registry
893 $applicationInstance = ApplicationHelper::getSelfInstance();
895 // Get the file instance ready
896 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
897 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
898 $applicationInstance->getAppShortName(),
900 $this->getMenuInstance()->getMenuName() . ':' .
901 $this->__toString() . ':' .
902 $this->getMenuInstance()->__toString()
904 $this->getMenuInstance()->getMenuType()
908 return $fileInstance;