3 namespace CoreFramework\Template\Engine;
5 // Import framework stuff
6 use CoreFramework\Factory\ObjectFactory;
7 use CoreFramework\Filesystem\InvalidDirectoryException;
8 use CoreFramework\Parser\Xml\XmlParser;
9 use CoreFramework\Registry\Registry;
10 use CoreFramework\Template\CompileableTemplate;
11 use CoreFramework\Template\Engine\BaseTemplateEngine;
14 use \UnexpectedValueException;
17 * A Menu template engine class
19 * @author Roland Haeder <webmaster@shipsimu.org>
21 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
22 * @license GNU GPL 3.0 or any newer version
23 * @link http://www.shipsimu.org
25 * This program is free software: you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation, either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <http://www.gnu.org/licenses/>.
38 class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate {
40 * Main nodes in the XML tree ('menu' is ignored)
42 private $mainNodes = array(
47 * Sub nodes in the XML tree
49 private $subNodes = array(
75 * Variables for a menu entry
77 private $menuEntryVariables = array(
88 * Variables for a menu block
90 private $menuBlockVariables = array(
95 // Content is taken from menuEntries
103 * Rendered menu entries
105 private $menuEntries = array();
108 * Rendered menu blocks
110 private $menuBlocks = array();
115 private $menuInstance = NULL;
120 private $curr = array();
123 * Content from dependency
125 private $dependencyContent = array();
128 * Protected constructor
132 protected function __construct () {
133 // Call parent constructor
134 parent::__construct(__CLASS__);
138 * Creates an instance of the class TemplateEngine and prepares it for usage
140 * @param $menuInstance A RenderableMenu instance
141 * @return $templateInstance An instance of TemplateEngine
142 * @throws UnexpectedValueException If the found $templateBasePath is empty or not a string
143 * @throws InvalidDirectoryException If $templateBasePath is no directory or not found
144 * @throws BasePathReadProtectedException If $templateBasePath is
147 public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) {
148 // Get a new instance
149 $templateInstance = new MenuTemplateEngine();
151 // Get the application instance from registry
152 $applicationInstance = Registry::getRegistry()->getInstance('app');
154 // Determine base path
155 $templateBasePath = $templateInstance->getConfigInstance()->getConfigEntry('application_base_path') . $applicationInstance->getAppShortName(). '/';
157 // Is the base path valid?
158 if (empty($templateBasePath)) {
159 // Base path is empty
160 throw new UnexpectedValueException(sprintf('[%s:%d] Variable templateBasePath is empty.', $templateInstance->__toString(), __LINE__), self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
161 } elseif (!is_string($templateBasePath)) {
163 throw new UnexpectedValueException(sprintf('[%s:%d] %s is not a string with a base path.', $templateInstance->__toString(), __LINE__, $templateBasePath), self::EXCEPTION_INVALID_STRING);
164 } elseif (!is_dir($templateBasePath)) {
166 throw new InvalidDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
167 } elseif (!is_readable($templateBasePath)) {
169 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
173 $templateInstance->setTemplateBasePath($templateBasePath);
175 // Set template extensions
176 $templateInstance->setRawTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('raw_template_extension'));
177 $templateInstance->setCodeTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('menu_template_extension'));
179 // Absolute output path for compiled templates
180 $templateInstance->setCompileOutputPath(sprintf('%s%s/',
182 $templateInstance->getConfigInstance()->getConfigEntry('compile_output_path')
185 // Set the menu instance
186 $templateInstance->setMenuInstance($menuInstance);
188 // Init a variable stacker
189 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
192 $templateInstance->setStackInstance($stackInstance);
194 // Return the prepared instance
195 return $templateInstance;
199 * Load a specified menu template into the engine
201 * @param $template The menu template we shall load which is
202 * located in 'menu' by default
205 public function loadMenuTemplate ($template) {
207 $this->setTemplateType($this->getConfigInstance()->getConfigEntry('menu_template_type'));
209 // Load the special template
210 $this->loadTemplate($template);
214 * Getter for current main node
216 * @return $currMainNode Current main node
218 public final function getCurrMainNode () {
219 return $this->curr['main_node'];
223 * Setter for current main node
225 * @param $element Element name to set as current main node
226 * @return $currMainNode Current main node
228 private final function setCurrMainNode ($element) {
229 $this->curr['main_node'] = (string) $element;
233 * Getter for main node array
235 * @return $mainNodes Array with valid main node names
237 public final function getMainNodes () {
238 return $this->mainNodes;
242 * Getter for sub node array
244 * @return $subNodes Array with valid sub node names
246 public final function getSubNodes () {
247 return $this->subNodes;
251 * Handles the start element of an XML resource
253 * @param $resource XML parser resource (currently ignored)
254 * @param $element The element we shall handle
255 * @param $attributes All attributes
257 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
259 public function startElement ($resource, $element, array $attributes) {
260 // Initial method name which will never be called...
261 $methodName = 'initMenu';
263 // Make the element name lower-case
264 $element = strtolower($element);
266 // Is the element a main node?
267 //* DEBUG: */ echo "START: >".$element."<<br />\n";
268 if (in_array($element, $this->getMainNodes())) {
269 // Okay, main node found!
270 $methodName = 'start' . self::convertToClassName($element);
273 $this->setCurrMainNode($element);
274 } elseif (in_array($element, $this->getSubNodes())) {
276 $methodName = 'start' . self::convertToClassName($element);
277 } elseif ($element != 'menu') {
278 // Invalid node name found
279 throw new InvalidXmlNodeException(array($this, $element, $attributes), XmlParser::EXCEPTION_XML_NODE_UNKNOWN);
283 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
284 call_user_func_array(array($this, $methodName), $attributes);
288 * Ends the main or sub node by sending out the gathered data
290 * @param $resource An XML resource pointer (currently ignored)
291 * @param $nodeName Name of the node we want to finish
293 * @throws XmlNodeMismatchException If current main node mismatches the closing one
295 public function finishElement ($resource, $nodeName) {
296 // Make all lower-case
297 $nodeName = strtolower($nodeName);
299 // Does this match with current main node?
300 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
301 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
303 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), XmlParser::EXCEPTION_XML_NODE_MISMATCH);
306 // Construct method name
307 $methodName = 'finish' . self::convertToClassName($nodeName);
309 // Call the corresponding method
310 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
311 call_user_func_array(array($this, $methodName), array());
317 * @param $resource XML parser resource (currently ignored)
318 * @param $characters Characters to handle
320 * @todo Find something useful with this!
322 public function characterHandler ($resource, $characters) {
323 // Trim all spaces away
324 $characters = trim($characters);
326 // Is this string empty?
327 if (empty($characters)) {
328 // Then skip it silently
332 // Assign the found characters to variable and use the last entry from
334 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
338 * Handles the template dependency for given node
340 * @param $node The node we should load a dependency template
341 * @param $templateDependency A template to load to satisfy dependencies
344 private function handleTemplateDependency ($node, $templateDependency) {
345 // Is the template dependency set?
346 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
347 // Get a temporay menu template instance
348 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
351 $templateInstance->loadMenuTemplate($templateDependency);
353 // Parse the XML content
354 $templateInstance->renderXmlContent();
356 // Save the parsed raw content in our dependency array
357 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
362 * Intializes the menu
364 * @param $templateDependency A template to load to satisfy dependencies
366 * @todo Add cache creation here
368 private function initMenu ($templateDependency = '') {
369 // Get web template engine
370 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array(Registry::getRegistry()->getInstance('app'))));
372 // Handle the dependency template
373 $this->handleTemplateDependency('menu', $templateDependency);
375 // Push the node name on the stacker
376 $this->getStackInstance()->pushNamed('current_node', 'menu');
380 * Starts the menu entries
382 * @param $templateDependency A template to load to satisfy dependencies
385 private function startEntryList () {
386 // Push the node name on the stacker
387 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
391 * Starts the menu block header
395 private function startBlockHeader () {
396 // Push the node name on the stacker
397 $this->getStackInstance()->pushNamed('current_node', 'block-header');
401 * Starts the menu block footer
405 private function startBlockFooter () {
406 // Push the node name on the stacker
407 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
411 * Starts the menu property 'block-list'
415 private function startBlockList () {
416 // Push the node name on the stacker
417 $this->getStackInstance()->pushNamed('current_node', 'block-list');
421 * Starts the menu property 'block'
425 private function startBlock () {
426 // Push the node name on the stacker
427 $this->getStackInstance()->pushNamed('current_node', 'block');
431 * Starts the menu property 'title'
435 private function startTitle () {
436 // Push the node name on the stacker
437 $this->getStackInstance()->pushNamed('current_node', 'title');
441 * Starts the menu property 'title-id'
445 private function startTitleId () {
446 // Push the node name on the stacker
447 $this->getStackInstance()->pushNamed('current_node', 'title-id');
451 * Starts the menu property 'title-class'
455 private function startTitleClass () {
456 // Push the node name on the stacker
457 $this->getStackInstance()->pushNamed('current_node', 'title-class');
461 * Starts the menu property 'title-text'
465 private function startTitleText () {
466 // Push the node name on the stacker
467 $this->getStackInstance()->pushNamed('current_node', 'title-text');
471 * Starts the menu property 'entry'
475 private function startEntry () {
476 // Push the node name on the stacker
477 $this->getStackInstance()->pushNamed('current_node', 'entry');
481 * Starts the menu property 'entry-id'
485 private function startEntryId () {
486 // Push the node name on the stacker
487 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
491 * Starts the menu property 'anchor'
495 private function startAnchor () {
496 // Push the node name on the stacker
497 $this->getStackInstance()->pushNamed('current_node', 'anchor');
501 * Starts the menu property 'anchor-id'
505 private function startAnchorId () {
506 // Push the node name on the stacker
507 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
511 * Starts the menu property 'anchor-text'
515 private function startAnchorText () {
516 // Push the node name on the stacker
517 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
521 * Starts the menu property 'anchor-title'
525 private function startAnchorTitle () {
526 // Push the node name on the stacker
527 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
531 * Starts the menu property 'anchor-href'
535 private function startAnchorHref () {
536 // Push the node name on the stacker
537 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
541 * Starts the menu property 'footer-id'
545 private function startFooterId () {
546 // Push the node name on the stacker
547 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
551 * Starts the menu property 'footer-class'
555 private function startFooterClass () {
556 // Push the node name on the stacker
557 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
561 * Starts the menu property 'footer-text'
565 private function startFooterText () {
566 // Push the node name on the stacker
567 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
571 * Finishes the title node by added another template to the menu
575 private function finishTitle () {
576 // Pop the last entry
577 $this->getStackInstance()->popNamed('current_node');
581 * Finishes the title-id node by
585 private function finishTitleId () {
586 // Pop the last entry
587 $this->getStackInstance()->popNamed('current_node');
591 * Finishes the title-class node
595 private function finishTitleClass () {
596 // Pop the last entry
597 $this->getStackInstance()->popNamed('current_node');
601 * Finishes the title-class node
605 private function finishTitleText () {
606 // Pop the last entry
607 $this->getStackInstance()->popNamed('current_node');
611 * Finishes the footer-text node
615 private function finishFooterText () {
616 // Pop the last entry
617 $this->getStackInstance()->popNamed('current_node');
621 * Finishes the footer-class node
625 private function finishFooterClass () {
626 // Pop the last entry
627 $this->getStackInstance()->popNamed('current_node');
631 * Finishes the footer-id node
635 private function finishFooterId () {
636 // Pop the last entry
637 $this->getStackInstance()->popNamed('current_node');
641 * Finishes the anchor-href node
645 private function finishAnchorHref () {
646 // Pop the last entry
647 $this->getStackInstance()->popNamed('current_node');
651 * Finishes the anchor-title node
655 private function finishAnchorTitle () {
656 // Pop the last entry
657 $this->getStackInstance()->popNamed('current_node');
661 * Finishes the anchor-text node
665 private function finishAnchorText () {
666 // Pop the last entry
667 $this->getStackInstance()->popNamed('current_node');
671 * Finishes the anchor-id node
675 private function finishAnchorId () {
676 // Pop the last entry
677 $this->getStackInstance()->popNamed('current_node');
681 * Finishes the anchor node
685 private function finishAnchor () {
686 // Pop the last entry
687 $this->getStackInstance()->popNamed('current_node');
691 * Finishes the entry-id node
695 private function finishEntryId () {
696 // Pop the last entry
697 $this->getStackInstance()->popNamed('current_node');
701 * Finishes the entry node
705 private function finishEntry () {
706 // Pop the last entry
707 $this->getStackInstance()->popNamed('current_node');
709 // Render this menu entry
710 $this->renderMenuEntry();
714 * Finishes the block node
718 private function finishBlock () {
719 // Pop the last entry
720 $this->getStackInstance()->popNamed('current_node');
722 // Render this menu block
723 $this->renderMenuBlock();
727 * Finishes the block-list node
731 private function finishBlockList () {
732 // Pop the last entry
733 $this->getStackInstance()->popNamed('current_node');
737 * Finishes the menu entries
741 private function finishEntryList () {
742 // Pop the last entry
743 $this->getStackInstance()->popNamed('current_node');
747 * Finishes the menu block header
751 private function finishBlockHeader () {
752 // Pop the last entry
753 $this->getStackInstance()->popNamed('current_node');
757 * Finishes the menu block footer
761 private function finishBlockFooter () {
762 // Pop the last entry
763 $this->getStackInstance()->popNamed('current_node');
771 private function finishMenu () {
772 // Pop the last entry
773 $this->getStackInstance()->popNamed('current_node');
777 * Renders this menu entry, as every block all variables got overwritten
778 * with data from next entry.
782 private function renderMenuEntry () {
783 // Prepare template engine
784 $templateInstance = $this->prepareTemplateInstance();
786 // Load menu entry template
787 $templateInstance->loadCodeTemplate('menu_entry');
789 // Copy all variables over to it
790 foreach ($this->menuEntryVariables as $variableName) {
792 $variableValue = $this->readVariable($variableName);
794 // Is the key 'anchor-href'?
795 if ($variableName == 'anchor-href') {
796 // Expand variable with URL then
797 $variableValue = '{?base_url?}/' . $variableValue;
800 // ... into the instance
801 $templateInstance->assignVariable($variableName, $variableValue);
804 // Compile template + variables
805 $templateInstance->compileTemplate();
806 $templateInstance->compileVariables();
809 $this->menuEntries[$this->readVariable('entry_id')] = $templateInstance->getRawTemplateData();
813 * Renders this menu block, as next block all data is overwritten with
818 private function renderMenuBlock () {
819 // Init block content
820 $blockContent = implode('', $this->menuEntries);
822 // Prepare template engine
823 $templateInstance = $this->prepareTemplateInstance();
825 // Load menu entry template
826 $templateInstance->loadCodeTemplate('menu_block');
828 // Copy all variables over to it
829 foreach ($this->menuBlockVariables as $variableName) {
831 $variableValue = $this->readVariable($variableName);
833 // ... into the instance
834 $templateInstance->assignVariable($variableName, $variableValue);
837 // Assign block content
838 $templateInstance->assignVariable('block_content', $blockContent);
840 // Compile template + variables
841 $templateInstance->compileTemplate();
842 $templateInstance->compileVariables();
845 array_push($this->menuBlocks, $templateInstance->getRawTemplateData());
847 // Reset rendered menu entries array
848 $this->menuEntries = array();
852 * "Getter" for menu content
854 * @return $menuContent Returned menu content
856 public function getMenuContent () {
857 // Implode menuBlocks
858 $menuContent = implode('', $this->menuBlocks);
861 $this->menuBlocks = array();
868 * Getter for menu cache file (FQFN)
870 * @return $fqfn Full-qualified file name of the menu cache
872 public function getMenuCacheFqfn () {
873 // Get the application instance from registry
874 $applicationInstance = Registry::getRegistry()->getInstance('app');
876 // Get the FQFN ready
877 $fqfn = sprintf('%s%smenus/_cache/%s.%s',
878 $this->getConfigInstance()->getConfigEntry('application_base_path'),
879 $applicationInstance->getAppShortName(),
881 $this->getMenuInstance()->getMenuName() . ':' .
882 $this->__toString() . ':' .
883 $this->getMenuInstance()->__toString()
885 $this->getMenuInstance()->getMenuType()