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\Xml\XmlParser;
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 = array(
51 * Sub nodes in the XML tree
53 private $subNodes = array(
79 * Variables for a menu entry
81 private $menuEntryVariables = array(
92 * Variables for a menu block
94 private $menuBlockVariables = array(
99 // Content is taken from menuEntries
107 * Rendered menu entries
109 private $menuEntries = array();
112 * Rendered menu blocks
114 private $menuBlocks = array();
119 private $curr = array();
122 * Content from dependency
124 private $dependencyContent = array();
129 private $menuInstance = NULL;
132 * Protected constructor
136 protected function __construct () {
137 // Call parent constructor
138 parent::__construct(__CLASS__);
142 * Creates an instance of the class TemplateEngine and prepares it for usage
144 * @param $menuInstance A RenderableMenu instance
145 * @return $templateInstance An instance of TemplateEngine
146 * @throws UnexpectedValueException If the found $templateBasePath is empty or not a string
147 * @throws InvalidDirectoryException If $templateBasePath is no directory or not found
148 * @throws BasePathReadProtectedException If $templateBasePath is
151 public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) {
152 // Get a new instance
153 $templateInstance = new MenuTemplateEngine();
155 // Get the application instance from registry
156 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
158 // Determine base path
159 $templateBasePath = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path') . $applicationInstance->getAppShortName(). '/';
161 // Is the base path valid?
162 if (empty($templateBasePath)) {
163 // Base path is empty
164 throw new UnexpectedValueException(sprintf('[%s:%d] Variable templateBasePath is empty.', $templateInstance->__toString(), __LINE__), self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
165 } elseif (!is_string($templateBasePath)) {
167 throw new UnexpectedValueException(sprintf('[%s:%d] %s is not a string with a base path.', $templateInstance->__toString(), __LINE__, $templateBasePath), self::EXCEPTION_INVALID_STRING);
168 } elseif (!is_dir($templateBasePath)) {
170 throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
171 } elseif (!is_readable($templateBasePath)) {
173 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
177 $templateInstance->setTemplateBasePath($templateBasePath);
179 // Set template extensions
180 $templateInstance->setRawTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('raw_template_extension'));
181 $templateInstance->setCodeTemplateExtension(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_extension'));
183 // Absolute output path for compiled templates
184 $templateInstance->setCompileOutputPath(sprintf('%s%s/',
186 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('compile_output_path')
189 // Set the menu instance
190 $templateInstance->setMenuInstance($menuInstance);
192 // Init a variable stacker
193 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
196 $templateInstance->setStackInstance($stackInstance);
198 // Return the prepared instance
199 return $templateInstance;
203 * Setter for the menu instance
205 * @param $menuInstance A RenderableMenu instance
208 protected final function setMenuInstance (RenderableMenu $menuInstance) {
209 $this->menuInstance = $menuInstance;
213 * Getter for the menu instance
215 * @return $menuInstance A RenderableMenu instance
217 private final function getMenuInstance () {
218 return $this->menuInstance;
222 * Load a specified menu template into the engine
224 * @param $template The menu template we shall load which is
225 * located in 'menu' by default
228 public function loadMenuTemplate ($template) {
230 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_type'));
232 // Load the special template
233 $this->loadTemplate($template);
237 * Getter for current main node
239 * @return $currMainNode Current main node
241 public final function getCurrMainNode () {
242 return $this->curr['main_node'];
246 * Setter for current main node
248 * @param $element Element name to set as current main node
249 * @return $currMainNode Current main node
251 private final function setCurrMainNode ($element) {
252 $this->curr['main_node'] = (string) $element;
256 * Getter for main node array
258 * @return $mainNodes Array with valid main node names
260 public final function getMainNodes () {
261 return $this->mainNodes;
265 * Getter for sub node array
267 * @return $subNodes Array with valid sub node names
269 public final function getSubNodes () {
270 return $this->subNodes;
274 * Handles the start element of an XML resource
276 * @param $resource XML parser resource (currently ignored)
277 * @param $element The element we shall handle
278 * @param $attributes All attributes
280 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
282 public function startElement ($resource, $element, array $attributes) {
283 // Initial method name which will never be called...
284 $methodName = 'initMenu';
286 // Make the element name lower-case
287 $element = strtolower($element);
289 // Is the element a main node?
290 //* DEBUG: */ echo "START: >".$element."<<br />\n";
291 if (in_array($element, $this->getMainNodes())) {
292 // Okay, main node found!
293 $methodName = 'start' . StringUtils::convertToClassName($element);
296 $this->setCurrMainNode($element);
297 } elseif (in_array($element, $this->getSubNodes())) {
299 $methodName = 'start' . StringUtils::convertToClassName($element);
300 } elseif ($element != 'menu') {
301 // Invalid node name found
302 throw new InvalidXmlNodeException(array($this, $element, $attributes), XmlParser::EXCEPTION_XML_NODE_UNKNOWN);
306 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
307 call_user_func_array(array($this, $methodName), $attributes);
311 * Ends the main or sub node by sending out the gathered data
313 * @param $resource An XML resource pointer (currently ignored)
314 * @param $nodeName Name of the node we want to finish
316 * @throws XmlNodeMismatchException If current main node mismatches the closing one
318 public function finishElement ($resource, $nodeName) {
319 // Make all lower-case
320 $nodeName = strtolower($nodeName);
322 // Does this match with current main node?
323 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
324 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
326 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), XmlParser::EXCEPTION_XML_NODE_MISMATCH);
329 // Construct method name
330 $methodName = 'finish' . StringUtils::convertToClassName($nodeName);
332 // Call the corresponding method
333 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
334 call_user_func_array(array($this, $methodName), array());
340 * @param $resource XML parser resource (currently ignored)
341 * @param $characters Characters to handle
343 * @todo Find something useful with this!
345 public function characterHandler ($resource, $characters) {
346 // Trim all spaces away
347 $characters = trim($characters);
349 // Is this string empty?
350 if (empty($characters)) {
351 // Then skip it silently
355 // Assign the found characters to variable and use the last entry from
357 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
361 * Handles the template dependency for given node
363 * @param $node The node we should load a dependency template
364 * @param $templateDependency A template to load to satisfy dependencies
367 private function handleTemplateDependency ($node, $templateDependency) {
368 // Is the template dependency set?
369 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
370 // Get a temporay menu template instance
371 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
374 $templateInstance->loadMenuTemplate($templateDependency);
376 // Parse the XML content
377 $templateInstance->renderXmlContent();
379 // Save the parsed raw content in our dependency array
380 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
385 * Intializes the menu
387 * @param $templateDependency A template to load to satisfy dependencies
389 * @todo Add cache creation here
391 private function initMenu ($templateDependency = '') {
392 // Get web template engine
393 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(GenericRegistry::getRegistry()->getInstance('application'))));
395 // Handle the dependency template
396 $this->handleTemplateDependency('menu', $templateDependency);
398 // Push the node name on the stacker
399 $this->getStackInstance()->pushNamed('current_node', 'menu');
403 * Starts the menu entries
405 * @param $templateDependency A template to load to satisfy dependencies
408 private function startEntryList () {
409 // Push the node name on the stacker
410 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
414 * Starts the menu block header
418 private function startBlockHeader () {
419 // Push the node name on the stacker
420 $this->getStackInstance()->pushNamed('current_node', 'block-header');
424 * Starts the menu block footer
428 private function startBlockFooter () {
429 // Push the node name on the stacker
430 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
434 * Starts the menu property 'block-list'
438 private function startBlockList () {
439 // Push the node name on the stacker
440 $this->getStackInstance()->pushNamed('current_node', 'block-list');
444 * Starts the menu property 'block'
448 private function startBlock () {
449 // Push the node name on the stacker
450 $this->getStackInstance()->pushNamed('current_node', 'block');
454 * Starts the menu property 'title'
458 private function startTitle () {
459 // Push the node name on the stacker
460 $this->getStackInstance()->pushNamed('current_node', 'title');
464 * Starts the menu property 'title-id'
468 private function startTitleId () {
469 // Push the node name on the stacker
470 $this->getStackInstance()->pushNamed('current_node', 'title-id');
474 * Starts the menu property 'title-class'
478 private function startTitleClass () {
479 // Push the node name on the stacker
480 $this->getStackInstance()->pushNamed('current_node', 'title-class');
484 * Starts the menu property 'title-text'
488 private function startTitleText () {
489 // Push the node name on the stacker
490 $this->getStackInstance()->pushNamed('current_node', 'title-text');
494 * Starts the menu property 'entry'
498 private function startEntry () {
499 // Push the node name on the stacker
500 $this->getStackInstance()->pushNamed('current_node', 'entry');
504 * Starts the menu property 'entry-id'
508 private function startEntryId () {
509 // Push the node name on the stacker
510 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
514 * Starts the menu property 'anchor'
518 private function startAnchor () {
519 // Push the node name on the stacker
520 $this->getStackInstance()->pushNamed('current_node', 'anchor');
524 * Starts the menu property 'anchor-id'
528 private function startAnchorId () {
529 // Push the node name on the stacker
530 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
534 * Starts the menu property 'anchor-text'
538 private function startAnchorText () {
539 // Push the node name on the stacker
540 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
544 * Starts the menu property 'anchor-title'
548 private function startAnchorTitle () {
549 // Push the node name on the stacker
550 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
554 * Starts the menu property 'anchor-href'
558 private function startAnchorHref () {
559 // Push the node name on the stacker
560 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
564 * Starts the menu property 'footer-id'
568 private function startFooterId () {
569 // Push the node name on the stacker
570 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
574 * Starts the menu property 'footer-class'
578 private function startFooterClass () {
579 // Push the node name on the stacker
580 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
584 * Starts the menu property 'footer-text'
588 private function startFooterText () {
589 // Push the node name on the stacker
590 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
594 * Finishes the title node by added another template to the menu
598 private function finishTitle () {
599 // Pop the last entry
600 $this->getStackInstance()->popNamed('current_node');
604 * Finishes the title-id node by
608 private function finishTitleId () {
609 // Pop the last entry
610 $this->getStackInstance()->popNamed('current_node');
614 * Finishes the title-class node
618 private function finishTitleClass () {
619 // Pop the last entry
620 $this->getStackInstance()->popNamed('current_node');
624 * Finishes the title-class node
628 private function finishTitleText () {
629 // Pop the last entry
630 $this->getStackInstance()->popNamed('current_node');
634 * Finishes the footer-text node
638 private function finishFooterText () {
639 // Pop the last entry
640 $this->getStackInstance()->popNamed('current_node');
644 * Finishes the footer-class node
648 private function finishFooterClass () {
649 // Pop the last entry
650 $this->getStackInstance()->popNamed('current_node');
654 * Finishes the footer-id node
658 private function finishFooterId () {
659 // Pop the last entry
660 $this->getStackInstance()->popNamed('current_node');
664 * Finishes the anchor-href node
668 private function finishAnchorHref () {
669 // Pop the last entry
670 $this->getStackInstance()->popNamed('current_node');
674 * Finishes the anchor-title node
678 private function finishAnchorTitle () {
679 // Pop the last entry
680 $this->getStackInstance()->popNamed('current_node');
684 * Finishes the anchor-text node
688 private function finishAnchorText () {
689 // Pop the last entry
690 $this->getStackInstance()->popNamed('current_node');
694 * Finishes the anchor-id node
698 private function finishAnchorId () {
699 // Pop the last entry
700 $this->getStackInstance()->popNamed('current_node');
704 * Finishes the anchor node
708 private function finishAnchor () {
709 // Pop the last entry
710 $this->getStackInstance()->popNamed('current_node');
714 * Finishes the entry-id node
718 private function finishEntryId () {
719 // Pop the last entry
720 $this->getStackInstance()->popNamed('current_node');
724 * Finishes the entry node
728 private function finishEntry () {
729 // Pop the last entry
730 $this->getStackInstance()->popNamed('current_node');
732 // Render this menu entry
733 $this->renderMenuEntry();
737 * Finishes the block node
741 private function finishBlock () {
742 // Pop the last entry
743 $this->getStackInstance()->popNamed('current_node');
745 // Render this menu block
746 $this->renderMenuBlock();
750 * Finishes the block-list node
754 private function finishBlockList () {
755 // Pop the last entry
756 $this->getStackInstance()->popNamed('current_node');
760 * Finishes the menu entries
764 private function finishEntryList () {
765 // Pop the last entry
766 $this->getStackInstance()->popNamed('current_node');
770 * Finishes the menu block header
774 private function finishBlockHeader () {
775 // Pop the last entry
776 $this->getStackInstance()->popNamed('current_node');
780 * Finishes the menu block footer
784 private function finishBlockFooter () {
785 // Pop the last entry
786 $this->getStackInstance()->popNamed('current_node');
794 private function finishMenu () {
795 // Pop the last entry
796 $this->getStackInstance()->popNamed('current_node');
800 * Renders this menu entry, as every block all variables got overwritten
801 * with data from next entry.
805 private function renderMenuEntry () {
806 // Load menu entry template
807 $this->getTemplateInstance()->loadCodeTemplate('menu_entry');
809 // Copy all variables over to it
810 foreach ($this->menuEntryVariables as $variableName) {
812 $variableValue = $this->readVariable($variableName);
814 // Is the key 'anchor-href'?
815 if ($variableName == 'anchor-href') {
816 // Expand variable with URL then
817 $variableValue = '{?base_url?}/' . $variableValue;
820 // ... into the instance
821 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
824 // Compile template + variables
825 $this->getTemplateInstance()->compileTemplate();
826 $this->getTemplateInstance()->compileVariables();
829 $this->menuEntries[$this->readVariable('entry_id')] = $this->getTemplateInstance()->getRawTemplateData();
833 * Renders this menu block, as next block all data is overwritten with
838 private function renderMenuBlock () {
839 // Init block content
840 $blockContent = implode('', $this->menuEntries);
842 // Load menu entry template
843 $this->getTemplateInstance()->loadCodeTemplate('menu_block');
845 // Copy all variables over to it
846 foreach ($this->menuBlockVariables as $variableName) {
848 $variableValue = $this->readVariable($variableName);
850 // ... into the instance
851 $this->getTemplateInstance()->assignVariable($variableName, $variableValue);
854 // Assign block content
855 $this->getTemplateInstance()->assignVariable('block_content', $blockContent);
857 // Compile template + variables
858 $this->getTemplateInstance()->compileTemplate();
859 $this->getTemplateInstance()->compileVariables();
862 array_push($this->menuBlocks, $this->getTemplateInstance()->getRawTemplateData());
864 // Reset rendered menu entries array
865 $this->menuEntries = array();
869 * "Getter" for menu content
871 * @return $menuContent Returned menu content
873 public function getMenuContent () {
874 // Implode menuBlocks
875 $menuContent = implode('', $this->menuBlocks);
878 $this->menuBlocks = array();
885 * Getter for menu cache file instance
887 * @return $fileInstance Full-qualified file name of the menu cache
889 public function getMenuCacheFile () {
890 // Get the application instance from registry
891 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
893 // Get the file instance ready
894 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
895 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
896 $applicationInstance->getAppShortName(),
898 $this->getMenuInstance()->getMenuName() . ':' .
899 $this->__toString() . ':' .
900 $this->getMenuInstance()->__toString()
902 $this->getMenuInstance()->getMenuType()
906 return $fileInstance;