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\Engine\BaseTemplateEngine;
13 use Org\Mxchange\CoreFramework\Template\CompileableTemplate;
14 use Org\Mxchange\CoreFramework\Traits\Stacker\StackableTrait;
15 use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
19 use \UnexpectedValueException;
22 * A Menu template engine class
24 * @author Roland Haeder <webmaster@shipsimu.org>
26 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
27 * @license GNU GPL 3.0 or any newer version
28 * @link http://www.shipsimu.org
30 * This program is free software: you can redistribute it and/or modify
31 * it under the terms of the GNU General Public License as published by
32 * the Free Software Foundation, either version 3 of the License, or
33 * (at your option) any later version.
35 * This program is distributed in the hope that it will be useful,
36 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 * GNU General Public License for more details.
40 * You should have received a copy of the GNU General Public License
41 * along with this program. If not, see <http://www.gnu.org/licenses/>.
43 class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate {
48 * Main nodes in the XML tree ('menu' is ignored)
50 private $mainNodes = [
55 * Sub nodes in the XML tree
83 * Variables for a menu entry
85 private $menuEntryVariables = [
96 * Variables for a menu block
98 private $menuBlockVariables = [
103 // Content is taken from menuEntries
111 * Rendered menu entries
113 private $menuEntries = [];
116 * Rendered menu blocks
118 private $menuBlocks = [];
126 * Content from dependency
128 private $dependencyContent = [];
133 private $menuInstance = NULL;
136 * Protected constructor
140 protected function __construct () {
141 // Call parent constructor
142 parent::__construct(__CLASS__);
146 * Creates an instance of the class TemplateEngine and prepares it for usage
148 * @param $menuInstance A RenderableMenu instance
149 * @return $templateInstance An instance of TemplateEngine
150 * @throws UnexpectedValueException If the found $templateBasePath is empty or not a string
151 * @throws InvalidDirectoryException If $templateBasePath is no directory or not found
152 * @throws BasePathReadProtectedException If $templateBasePath is
155 public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) {
156 // Get a new instance
157 $templateInstance = new MenuTemplateEngine();
159 // Get the application instance from registry
160 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
162 // Determine base path
163 $templateBasePath = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path') . $applicationInstance->getAppShortName(). '/';
165 // Is the base path valid?
166 if (empty($templateBasePath)) {
167 // Base path is empty
168 throw new UnexpectedValueException(sprintf('[%s:%d] Variable templateBasePath is empty.', $templateInstance->__toString(), __LINE__), self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
169 } elseif (!is_string($templateBasePath)) {
171 throw new UnexpectedValueException(sprintf('[%s:%d] %s is not a string with a base path.', $templateInstance->__toString(), __LINE__, $templateBasePath), self::EXCEPTION_INVALID_STRING);
172 } elseif (!is_dir($templateBasePath)) {
174 throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
175 } elseif (!is_readable($templateBasePath)) {
177 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
181 $templateInstance->setTemplateBasePath($templateBasePath);
183 // Set template extensions
184 $templateInstance->setRawTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('raw_template_extension'));
185 $templateInstance->setCodeTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_extension'));
187 // Absolute output path for compiled templates
188 $templateInstance->setCompileOutputPath(sprintf('%s%s/',
190 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('compile_output_path')
193 // Set the menu instance
194 $templateInstance->setMenuInstance($menuInstance);
196 // Init a variable stacker
197 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
200 $templateInstance->setStackInstance($stackInstance);
202 // Return the prepared instance
203 return $templateInstance;
207 * Setter for the menu instance
209 * @param $menuInstance A RenderableMenu instance
212 protected final function setMenuInstance (RenderableMenu $menuInstance) {
213 $this->menuInstance = $menuInstance;
217 * Getter for the menu instance
219 * @return $menuInstance A RenderableMenu instance
221 private final function getMenuInstance () {
222 return $this->menuInstance;
226 * Load a specified menu template into the engine
228 * @param $template The menu template we shall load which is
229 * located in 'menu' by default
232 public function loadMenuTemplate (string $template) {
234 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_type'));
236 // Load the special template
237 $this->loadTemplate($template);
241 * Getter for current main node
243 * @return $currMainNode Current main node
245 public final function getCurrMainNode () {
246 return $this->curr['main_node'];
250 * Setter for current main node
252 * @param $element Element name to set as current main node
253 * @return $currMainNode Current main node
255 private final function setCurrMainNode (string $element) {
256 $this->curr['main_node'] = $element;
260 * Getter for main node array
262 * @return $mainNodes Array with valid main node names
264 public final function getMainNodes () {
265 return $this->mainNodes;
269 * Getter for sub node array
271 * @return $subNodes Array with valid sub node names
273 public final function getSubNodes () {
274 return $this->subNodes;
278 * Handles the start element of an XML resource
280 * @param $resource XML parser resource (currently ignored)
281 * @param $element The element we shall handle
282 * @param $attributes All attributes
284 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
286 public function startElement ($resource, string $element, array $attributes) {
287 // Initial method name which will never be called...
288 $methodName = 'initMenu';
290 // Make the element name lower-case
291 $element = strtolower($element);
293 // Is the element a main node?
294 //* DEBUG: */ echo "START: >".$element."<<br />\n";
295 if (in_array($element, $this->getMainNodes())) {
296 // Okay, main node found!
297 $methodName = 'start' . StringUtils::convertToClassName($element);
300 $this->setCurrMainNode($element);
301 } elseif (in_array($element, $this->getSubNodes())) {
303 $methodName = 'start' . StringUtils::convertToClassName($element);
304 } elseif ($element != 'menu') {
305 // Invalid node name found
306 throw new InvalidXmlNodeException(array($this, $element, $attributes), Parseable::EXCEPTION_XML_NODE_UNKNOWN);
310 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
311 call_user_func_array(array($this, $methodName), $attributes);
315 * Ends the main or sub node by sending out the gathered data
317 * @param $resource An XML resource pointer (currently ignored)
318 * @param $nodeName Name of the node we want to finish
320 * @throws XmlNodeMismatchException If current main node mismatches the closing one
322 public function finishElement ($resource, string $nodeName) {
323 // Does this match with current main node?
324 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
325 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
327 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), Parseable::EXCEPTION_XML_NODE_MISMATCH);
330 // Construct method name
331 $methodName = 'finish' . StringUtils::convertToClassName($nodeName);
333 // Call the corresponding method
334 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
335 call_user_func_array(array($this, $methodName), []);
341 * @param $resource XML parser resource (currently ignored)
342 * @param $characters Characters to handle
344 * @todo Find something useful with this!
346 public function characterHandler ($resource, string $characters) {
347 // Trim all spaces away
348 $characters = trim($characters);
350 // Is this string empty?
351 if (empty($characters)) {
352 // Then skip it silently
356 // Assign the found characters to variable and use the last entry from
358 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
362 * Handles the template dependency for given node
364 * @param $node The node we should load a dependency template
365 * @param $templateDependency A template to load to satisfy dependencies
368 private function handleTemplateDependency ($node, $templateDependency) {
369 // Is the template dependency set?
370 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
371 // Get a temporay menu template instance
372 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
375 $templateInstance->loadMenuTemplate($templateDependency);
377 // Parse the XML content
378 $templateInstance->renderXmlContent();
380 // Save the parsed raw content in our dependency array
381 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
386 * Intializes the menu
388 * @param $templateDependency A template to load to satisfy dependencies
390 * @todo Add cache creation here
392 private function initMenu ($templateDependency = '') {
393 // Get web template engine
394 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(GenericRegistry::getRegistry()->getInstance('application'))));
396 // Handle the dependency template
397 $this->handleTemplateDependency('menu', $templateDependency);
399 // Push the node name on the stacker
400 $this->getStackInstance()->pushNamed('current_node', 'menu');
404 * Starts the menu entries
406 * @param $templateDependency A template to load to satisfy dependencies
409 private function startEntryList () {
410 // Push the node name on the stacker
411 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
415 * Starts the menu block header
419 private function startBlockHeader () {
420 // Push the node name on the stacker
421 $this->getStackInstance()->pushNamed('current_node', 'block-header');
425 * Starts the menu block footer
429 private function startBlockFooter () {
430 // Push the node name on the stacker
431 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
435 * Starts the menu property 'block-list'
439 private function startBlockList () {
440 // Push the node name on the stacker
441 $this->getStackInstance()->pushNamed('current_node', 'block-list');
445 * Starts the menu property 'block'
449 private function startBlock () {
450 // Push the node name on the stacker
451 $this->getStackInstance()->pushNamed('current_node', 'block');
455 * Starts the menu property 'title'
459 private function startTitle () {
460 // Push the node name on the stacker
461 $this->getStackInstance()->pushNamed('current_node', 'title');
465 * Starts the menu property 'title-id'
469 private function startTitleId () {
470 // Push the node name on the stacker
471 $this->getStackInstance()->pushNamed('current_node', 'title-id');
475 * Starts the menu property 'title-class'
479 private function startTitleClass () {
480 // Push the node name on the stacker
481 $this->getStackInstance()->pushNamed('current_node', 'title-class');
485 * Starts the menu property 'title-text'
489 private function startTitleText () {
490 // Push the node name on the stacker
491 $this->getStackInstance()->pushNamed('current_node', 'title-text');
495 * Starts the menu property 'entry'
499 private function startEntry () {
500 // Push the node name on the stacker
501 $this->getStackInstance()->pushNamed('current_node', 'entry');
505 * Starts the menu property 'entry-id'
509 private function startEntryId () {
510 // Push the node name on the stacker
511 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
515 * Starts the menu property 'anchor'
519 private function startAnchor () {
520 // Push the node name on the stacker
521 $this->getStackInstance()->pushNamed('current_node', 'anchor');
525 * Starts the menu property 'anchor-id'
529 private function startAnchorId () {
530 // Push the node name on the stacker
531 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
535 * Starts the menu property 'anchor-text'
539 private function startAnchorText () {
540 // Push the node name on the stacker
541 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
545 * Starts the menu property 'anchor-title'
549 private function startAnchorTitle () {
550 // Push the node name on the stacker
551 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
555 * Starts the menu property 'anchor-href'
559 private function startAnchorHref () {
560 // Push the node name on the stacker
561 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
565 * Starts the menu property 'footer-id'
569 private function startFooterId () {
570 // Push the node name on the stacker
571 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
575 * Starts the menu property 'footer-class'
579 private function startFooterClass () {
580 // Push the node name on the stacker
581 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
585 * Starts the menu property 'footer-text'
589 private function startFooterText () {
590 // Push the node name on the stacker
591 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
595 * Finishes the title node by added another template to the menu
599 private function finishTitle () {
600 // Pop the last entry
601 $this->getStackInstance()->popNamed('current_node');
605 * Finishes the title-id node by
609 private function finishTitleId () {
610 // Pop the last entry
611 $this->getStackInstance()->popNamed('current_node');
615 * Finishes the title-class node
619 private function finishTitleClass () {
620 // Pop the last entry
621 $this->getStackInstance()->popNamed('current_node');
625 * Finishes the title-class node
629 private function finishTitleText () {
630 // Pop the last entry
631 $this->getStackInstance()->popNamed('current_node');
635 * Finishes the footer-text node
639 private function finishFooterText () {
640 // Pop the last entry
641 $this->getStackInstance()->popNamed('current_node');
645 * Finishes the footer-class node
649 private function finishFooterClass () {
650 // Pop the last entry
651 $this->getStackInstance()->popNamed('current_node');
655 * Finishes the footer-id node
659 private function finishFooterId () {
660 // Pop the last entry
661 $this->getStackInstance()->popNamed('current_node');
665 * Finishes the anchor-href node
669 private function finishAnchorHref () {
670 // Pop the last entry
671 $this->getStackInstance()->popNamed('current_node');
675 * Finishes the anchor-title node
679 private function finishAnchorTitle () {
680 // Pop the last entry
681 $this->getStackInstance()->popNamed('current_node');
685 * Finishes the anchor-text node
689 private function finishAnchorText () {
690 // Pop the last entry
691 $this->getStackInstance()->popNamed('current_node');
695 * Finishes the anchor-id node
699 private function finishAnchorId () {
700 // Pop the last entry
701 $this->getStackInstance()->popNamed('current_node');
705 * Finishes the anchor node
709 private function finishAnchor () {
710 // Pop the last entry
711 $this->getStackInstance()->popNamed('current_node');
715 * Finishes the entry-id node
719 private function finishEntryId () {
720 // Pop the last entry
721 $this->getStackInstance()->popNamed('current_node');
725 * Finishes the entry node
729 private function finishEntry () {
730 // Pop the last entry
731 $this->getStackInstance()->popNamed('current_node');
733 // Render this menu entry
734 $this->renderMenuEntry();
738 * Finishes the block node
742 private function finishBlock () {
743 // Pop the last entry
744 $this->getStackInstance()->popNamed('current_node');
746 // Render this menu block
747 $this->renderMenuBlock();
751 * Finishes the block-list node
755 private function finishBlockList () {
756 // Pop the last entry
757 $this->getStackInstance()->popNamed('current_node');
761 * Finishes the menu entries
765 private function finishEntryList () {
766 // Pop the last entry
767 $this->getStackInstance()->popNamed('current_node');
771 * Finishes the menu block header
775 private function finishBlockHeader () {
776 // Pop the last entry
777 $this->getStackInstance()->popNamed('current_node');
781 * Finishes the menu block footer
785 private function finishBlockFooter () {
786 // Pop the last entry
787 $this->getStackInstance()->popNamed('current_node');
795 private function finishMenu () {
796 // Pop the last entry
797 $this->getStackInstance()->popNamed('current_node');
801 * Renders this menu entry, as every block all variables got overwritten
802 * with data from next entry.
806 private function renderMenuEntry () {
807 // Load menu entry template
808 $this->getTemplateInstance()->loadCodeTemplate('menu_entry');
810 // Copy all variables over to it
811 foreach ($this->menuEntryVariables as $variableName) {
813 $variableValue = $this->readVariable($variableName);
815 // Is the key 'anchor-href'?
816 if ($variableName == 'anchor-href') {
817 // Expand variable with URL then
818 $variableValue = '{?base_url?}/' . $variableValue;
821 // ... into the instance
822 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
825 // Compile template + variables
826 $this->getTemplateInstance()->compileTemplate();
827 $this->getTemplateInstance()->compileVariables();
830 $this->menuEntries[$this->readVariable('entry_id')] = $this->getTemplateInstance()->getRawTemplateData();
834 * Renders this menu block, as next block all data is overwritten with
839 private function renderMenuBlock () {
840 // Init block content
841 $blockContent = implode('', $this->menuEntries);
843 // Load menu entry template
844 $this->getTemplateInstance()->loadCodeTemplate('menu_block');
846 // Copy all variables over to it
847 foreach ($this->menuBlockVariables as $variableName) {
849 $variableValue = $this->readVariable($variableName);
851 // ... into the instance
852 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
855 // Assign block content
856 $this->getTemplateInstance()->assignVariable('block_content', $blockContent);
858 // Compile template + variables
859 $this->getTemplateInstance()->compileTemplate();
860 $this->getTemplateInstance()->compileVariables();
863 array_push($this->menuBlocks, $this->getTemplateInstance()->getRawTemplateData());
865 // Reset rendered menu entries array
866 $this->menuEntries = [];
870 * "Getter" for menu content
872 * @return $menuContent Returned menu content
874 public function getMenuContent () {
875 // Implode menuBlocks
876 $menuContent = implode('', $this->menuBlocks);
879 $this->menuBlocks = [];
886 * Getter for menu cache file instance
888 * @return $fileInstance Full-qualified file name of the menu cache
890 public function getMenuCacheFile () {
891 // Get the application instance from registry
892 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
894 // Get the file instance ready
895 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
896 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
897 $applicationInstance->getAppShortName(),
899 $this->getMenuInstance()->getMenuName() . ':' .
900 $this->__toString() . ':' .
901 $this->getMenuInstance()->__toString()
903 $this->getMenuInstance()->getMenuType()
907 return $fileInstance;