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 $menuInstance = NULL;
124 private $curr = array();
127 * Content from dependency
129 private $dependencyContent = array();
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 * Load a specified menu template into the engine
205 * @param $template The menu template we shall load which is
206 * located in 'menu' by default
209 public function loadMenuTemplate ($template) {
211 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('menu_template_type'));
213 // Load the special template
214 $this->loadTemplate($template);
218 * Getter for current main node
220 * @return $currMainNode Current main node
222 public final function getCurrMainNode () {
223 return $this->curr['main_node'];
227 * Setter for current main node
229 * @param $element Element name to set as current main node
230 * @return $currMainNode Current main node
232 private final function setCurrMainNode ($element) {
233 $this->curr['main_node'] = (string) $element;
237 * Getter for main node array
239 * @return $mainNodes Array with valid main node names
241 public final function getMainNodes () {
242 return $this->mainNodes;
246 * Getter for sub node array
248 * @return $subNodes Array with valid sub node names
250 public final function getSubNodes () {
251 return $this->subNodes;
255 * Handles the start element of an XML resource
257 * @param $resource XML parser resource (currently ignored)
258 * @param $element The element we shall handle
259 * @param $attributes All attributes
261 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
263 public function startElement ($resource, $element, array $attributes) {
264 // Initial method name which will never be called...
265 $methodName = 'initMenu';
267 // Make the element name lower-case
268 $element = strtolower($element);
270 // Is the element a main node?
271 //* DEBUG: */ echo "START: >".$element."<<br />\n";
272 if (in_array($element, $this->getMainNodes())) {
273 // Okay, main node found!
274 $methodName = 'start' . StringUtils::convertToClassName($element);
277 $this->setCurrMainNode($element);
278 } elseif (in_array($element, $this->getSubNodes())) {
280 $methodName = 'start' . StringUtils::convertToClassName($element);
281 } elseif ($element != 'menu') {
282 // Invalid node name found
283 throw new InvalidXmlNodeException(array($this, $element, $attributes), XmlParser::EXCEPTION_XML_NODE_UNKNOWN);
287 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
288 call_user_func_array(array($this, $methodName), $attributes);
292 * Ends the main or sub node by sending out the gathered data
294 * @param $resource An XML resource pointer (currently ignored)
295 * @param $nodeName Name of the node we want to finish
297 * @throws XmlNodeMismatchException If current main node mismatches the closing one
299 public function finishElement ($resource, $nodeName) {
300 // Make all lower-case
301 $nodeName = strtolower($nodeName);
303 // Does this match with current main node?
304 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
305 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
307 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), XmlParser::EXCEPTION_XML_NODE_MISMATCH);
310 // Construct method name
311 $methodName = 'finish' . StringUtils::convertToClassName($nodeName);
313 // Call the corresponding method
314 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
315 call_user_func_array(array($this, $methodName), array());
321 * @param $resource XML parser resource (currently ignored)
322 * @param $characters Characters to handle
324 * @todo Find something useful with this!
326 public function characterHandler ($resource, $characters) {
327 // Trim all spaces away
328 $characters = trim($characters);
330 // Is this string empty?
331 if (empty($characters)) {
332 // Then skip it silently
336 // Assign the found characters to variable and use the last entry from
338 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
342 * Handles the template dependency for given node
344 * @param $node The node we should load a dependency template
345 * @param $templateDependency A template to load to satisfy dependencies
348 private function handleTemplateDependency ($node, $templateDependency) {
349 // Is the template dependency set?
350 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
351 // Get a temporay menu template instance
352 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
355 $templateInstance->loadMenuTemplate($templateDependency);
357 // Parse the XML content
358 $templateInstance->renderXmlContent();
360 // Save the parsed raw content in our dependency array
361 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
366 * Intializes the menu
368 * @param $templateDependency A template to load to satisfy dependencies
370 * @todo Add cache creation here
372 private function initMenu ($templateDependency = '') {
373 // Get web template engine
374 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(GenericRegistry::getRegistry()->getInstance('application'))));
376 // Handle the dependency template
377 $this->handleTemplateDependency('menu', $templateDependency);
379 // Push the node name on the stacker
380 $this->getStackInstance()->pushNamed('current_node', 'menu');
384 * Starts the menu entries
386 * @param $templateDependency A template to load to satisfy dependencies
389 private function startEntryList () {
390 // Push the node name on the stacker
391 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
395 * Starts the menu block header
399 private function startBlockHeader () {
400 // Push the node name on the stacker
401 $this->getStackInstance()->pushNamed('current_node', 'block-header');
405 * Starts the menu block footer
409 private function startBlockFooter () {
410 // Push the node name on the stacker
411 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
415 * Starts the menu property 'block-list'
419 private function startBlockList () {
420 // Push the node name on the stacker
421 $this->getStackInstance()->pushNamed('current_node', 'block-list');
425 * Starts the menu property 'block'
429 private function startBlock () {
430 // Push the node name on the stacker
431 $this->getStackInstance()->pushNamed('current_node', 'block');
435 * Starts the menu property 'title'
439 private function startTitle () {
440 // Push the node name on the stacker
441 $this->getStackInstance()->pushNamed('current_node', 'title');
445 * Starts the menu property 'title-id'
449 private function startTitleId () {
450 // Push the node name on the stacker
451 $this->getStackInstance()->pushNamed('current_node', 'title-id');
455 * Starts the menu property 'title-class'
459 private function startTitleClass () {
460 // Push the node name on the stacker
461 $this->getStackInstance()->pushNamed('current_node', 'title-class');
465 * Starts the menu property 'title-text'
469 private function startTitleText () {
470 // Push the node name on the stacker
471 $this->getStackInstance()->pushNamed('current_node', 'title-text');
475 * Starts the menu property 'entry'
479 private function startEntry () {
480 // Push the node name on the stacker
481 $this->getStackInstance()->pushNamed('current_node', 'entry');
485 * Starts the menu property 'entry-id'
489 private function startEntryId () {
490 // Push the node name on the stacker
491 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
495 * Starts the menu property 'anchor'
499 private function startAnchor () {
500 // Push the node name on the stacker
501 $this->getStackInstance()->pushNamed('current_node', 'anchor');
505 * Starts the menu property 'anchor-id'
509 private function startAnchorId () {
510 // Push the node name on the stacker
511 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
515 * Starts the menu property 'anchor-text'
519 private function startAnchorText () {
520 // Push the node name on the stacker
521 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
525 * Starts the menu property 'anchor-title'
529 private function startAnchorTitle () {
530 // Push the node name on the stacker
531 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
535 * Starts the menu property 'anchor-href'
539 private function startAnchorHref () {
540 // Push the node name on the stacker
541 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
545 * Starts the menu property 'footer-id'
549 private function startFooterId () {
550 // Push the node name on the stacker
551 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
555 * Starts the menu property 'footer-class'
559 private function startFooterClass () {
560 // Push the node name on the stacker
561 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
565 * Starts the menu property 'footer-text'
569 private function startFooterText () {
570 // Push the node name on the stacker
571 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
575 * Finishes the title node by added another template to the menu
579 private function finishTitle () {
580 // Pop the last entry
581 $this->getStackInstance()->popNamed('current_node');
585 * Finishes the title-id node by
589 private function finishTitleId () {
590 // Pop the last entry
591 $this->getStackInstance()->popNamed('current_node');
595 * Finishes the title-class node
599 private function finishTitleClass () {
600 // Pop the last entry
601 $this->getStackInstance()->popNamed('current_node');
605 * Finishes the title-class node
609 private function finishTitleText () {
610 // Pop the last entry
611 $this->getStackInstance()->popNamed('current_node');
615 * Finishes the footer-text node
619 private function finishFooterText () {
620 // Pop the last entry
621 $this->getStackInstance()->popNamed('current_node');
625 * Finishes the footer-class node
629 private function finishFooterClass () {
630 // Pop the last entry
631 $this->getStackInstance()->popNamed('current_node');
635 * Finishes the footer-id node
639 private function finishFooterId () {
640 // Pop the last entry
641 $this->getStackInstance()->popNamed('current_node');
645 * Finishes the anchor-href node
649 private function finishAnchorHref () {
650 // Pop the last entry
651 $this->getStackInstance()->popNamed('current_node');
655 * Finishes the anchor-title node
659 private function finishAnchorTitle () {
660 // Pop the last entry
661 $this->getStackInstance()->popNamed('current_node');
665 * Finishes the anchor-text node
669 private function finishAnchorText () {
670 // Pop the last entry
671 $this->getStackInstance()->popNamed('current_node');
675 * Finishes the anchor-id node
679 private function finishAnchorId () {
680 // Pop the last entry
681 $this->getStackInstance()->popNamed('current_node');
685 * Finishes the anchor node
689 private function finishAnchor () {
690 // Pop the last entry
691 $this->getStackInstance()->popNamed('current_node');
695 * Finishes the entry-id node
699 private function finishEntryId () {
700 // Pop the last entry
701 $this->getStackInstance()->popNamed('current_node');
705 * Finishes the entry node
709 private function finishEntry () {
710 // Pop the last entry
711 $this->getStackInstance()->popNamed('current_node');
713 // Render this menu entry
714 $this->renderMenuEntry();
718 * Finishes the block node
722 private function finishBlock () {
723 // Pop the last entry
724 $this->getStackInstance()->popNamed('current_node');
726 // Render this menu block
727 $this->renderMenuBlock();
731 * Finishes the block-list node
735 private function finishBlockList () {
736 // Pop the last entry
737 $this->getStackInstance()->popNamed('current_node');
741 * Finishes the menu entries
745 private function finishEntryList () {
746 // Pop the last entry
747 $this->getStackInstance()->popNamed('current_node');
751 * Finishes the menu block header
755 private function finishBlockHeader () {
756 // Pop the last entry
757 $this->getStackInstance()->popNamed('current_node');
761 * Finishes the menu block footer
765 private function finishBlockFooter () {
766 // Pop the last entry
767 $this->getStackInstance()->popNamed('current_node');
775 private function finishMenu () {
776 // Pop the last entry
777 $this->getStackInstance()->popNamed('current_node');
781 * Renders this menu entry, as every block all variables got overwritten
782 * with data from next entry.
786 private function renderMenuEntry () {
787 // Prepare template engine
788 $templateInstance = $this->prepareTemplateInstance();
790 // Load menu entry template
791 $templateInstance->loadCodeTemplate('menu_entry');
793 // Copy all variables over to it
794 foreach ($this->menuEntryVariables as $variableName) {
796 $variableValue = $this->readVariable($variableName);
798 // Is the key 'anchor-href'?
799 if ($variableName == 'anchor-href') {
800 // Expand variable with URL then
801 $variableValue = '{?base_url?}/' . $variableValue;
804 // ... into the instance
805 $templateInstance->assignVariable($variableName, $variableValue);
808 // Compile template + variables
809 $templateInstance->compileTemplate();
810 $templateInstance->compileVariables();
813 $this->menuEntries[$this->readVariable('entry_id')] = $templateInstance->getRawTemplateData();
817 * Renders this menu block, as next block all data is overwritten with
822 private function renderMenuBlock () {
823 // Init block content
824 $blockContent = implode('', $this->menuEntries);
826 // Prepare template engine
827 $templateInstance = $this->prepareTemplateInstance();
829 // Load menu entry template
830 $templateInstance->loadCodeTemplate('menu_block');
832 // Copy all variables over to it
833 foreach ($this->menuBlockVariables as $variableName) {
835 $variableValue = $this->readVariable($variableName);
837 // ... into the instance
838 $templateInstance->assignVariable($variableName, $variableValue);
841 // Assign block content
842 $templateInstance->assignVariable('block_content', $blockContent);
844 // Compile template + variables
845 $templateInstance->compileTemplate();
846 $templateInstance->compileVariables();
849 array_push($this->menuBlocks, $templateInstance->getRawTemplateData());
851 // Reset rendered menu entries array
852 $this->menuEntries = array();
856 * "Getter" for menu content
858 * @return $menuContent Returned menu content
860 public function getMenuContent () {
861 // Implode menuBlocks
862 $menuContent = implode('', $this->menuBlocks);
865 $this->menuBlocks = array();
872 * Getter for menu cache file instance
874 * @return $fileInstance Full-qualified file name of the menu cache
876 public function getMenuCacheFile () {
877 // Get the application instance from registry
878 $applicationInstance = GenericRegistry::getRegistry()->getInstance('application');
880 // Get the file instance ready
881 $fileInstance = new SplFileInfo(sprintf('%s%smenus/_cache/%s.%s',
882 FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('application_base_path'),
883 $applicationInstance->getAppShortName(),
885 $this->getMenuInstance()->getMenuName() . ':' .
886 $this->__toString() . ':' .
887 $this->getMenuInstance()->__toString()
889 $this->getMenuInstance()->getMenuType()
893 return $fileInstance;