3 namespace CoreFramework\Template\Engine;
5 // Import framework stuff
6 use CoreFramework\Factory\ObjectFactory;
7 use CoreFramework\Parser\Xml\XmlParser;
8 use CoreFramework\Registry\Registry;
9 use CoreFramework\Template\CompileableTemplate;
12 * A Menu template engine class
14 * @author Roland Haeder <webmaster@shipsimu.org>
16 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
17 * @license GNU GPL 3.0 or any newer version
18 * @link http://www.shipsimu.org
20 * This program is free software: you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation, either version 3 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <http://www.gnu.org/licenses/>.
33 class MenuTemplateEngine extends BaseTemplateEngine implements CompileableTemplate {
35 * Main nodes in the XML tree ('menu' is ignored)
37 private $mainNodes = array(
42 * Sub nodes in the XML tree
44 private $subNodes = array(
70 * Variables for a menu entry
72 private $menuEntryVariables = array(
83 * Variables for a menu block
85 private $menuBlockVariables = array(
90 // Content is taken from menuEntries
98 * Rendered menu entries
100 private $menuEntries = array();
103 * Rendered menu blocks
105 private $menuBlocks = array();
110 private $menuInstance = NULL;
115 private $curr = array();
118 * Content from dependency
120 private $dependencyContent = array();
123 * Protected constructor
127 protected function __construct () {
128 // Call parent constructor
129 parent::__construct(__CLASS__);
133 * Creates an instance of the class TemplateEngine and prepares it for usage
135 * @param $menuInstance A RenderableMenu instance
136 * @return $templateInstance An instance of TemplateEngine
137 * @throws BasePathIsEmptyException If the provided $templateBasePath is empty
138 * @throws InvalidBasePathStringException If $templateBasePath is no string
139 * @throws BasePathIsNoDirectoryException If $templateBasePath is no
140 * directory or not found
141 * @throws BasePathReadProtectedException If $templateBasePath is
144 public static final function createMenuTemplateEngine (RenderableMenu $menuInstance) {
145 // Get a new instance
146 $templateInstance = new MenuTemplateEngine();
148 // Get the application instance from registry
149 $applicationInstance = Registry::getRegistry()->getInstance('app');
151 // Determine base path
152 $templateBasePath = $templateInstance->getConfigInstance()->getConfigEntry('application_base_path') . $applicationInstance->getRequestInstance()->getRequestElement('app') . '/';
154 // Is the base path valid?
155 if (empty($templateBasePath)) {
156 // Base path is empty
157 throw new BasePathIsEmptyException($templateInstance, self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
158 } elseif (!is_string($templateBasePath)) {
160 throw new InvalidBasePathStringException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_STRING);
161 } elseif (!is_dir($templateBasePath)) {
163 throw new BasePathIsNoDirectoryException(array($templateInstance, $templateBasePath), self::EXCEPTION_INVALID_PATH_NAME);
164 } elseif (!is_readable($templateBasePath)) {
166 throw new BasePathReadProtectedException(array($templateInstance, $templateBasePath), self::EXCEPTION_READ_PROTECED_PATH);
170 $templateInstance->setTemplateBasePath($templateBasePath);
172 // Set template extensions
173 $templateInstance->setRawTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('raw_template_extension'));
174 $templateInstance->setCodeTemplateExtension($templateInstance->getConfigInstance()->getConfigEntry('menu_template_extension'));
176 // Absolute output path for compiled templates
177 $templateInstance->setCompileOutputPath($templateInstance->getConfigInstance()->getConfigEntry('base_path') . $templateInstance->getConfigInstance()->getConfigEntry('compile_output_path'));
179 // Set the menu instance
180 $templateInstance->setMenuInstance($menuInstance);
182 // Init a variable stacker
183 $stackInstance = ObjectFactory::createObjectByConfiguredName('menu_stacker_class');
186 $templateInstance->setStackInstance($stackInstance);
188 // Return the prepared instance
189 return $templateInstance;
193 * Load a specified menu template into the engine
195 * @param $template The menu template we shall load which is
196 * located in 'menu' by default
199 public function loadMenuTemplate ($template) {
201 $this->setTemplateType($this->getConfigInstance()->getConfigEntry('menu_template_type'));
203 // Load the special template
204 $this->loadTemplate($template);
208 * Getter for current main node
210 * @return $currMainNode Current main node
212 public final function getCurrMainNode () {
213 return $this->curr['main_node'];
217 * Setter for current main node
219 * @param $element Element name to set as current main node
220 * @return $currMainNode Current main node
222 private final function setCurrMainNode ($element) {
223 $this->curr['main_node'] = (string) $element;
227 * Getter for main node array
229 * @return $mainNodes Array with valid main node names
231 public final function getMainNodes () {
232 return $this->mainNodes;
236 * Getter for sub node array
238 * @return $subNodes Array with valid sub node names
240 public final function getSubNodes () {
241 return $this->subNodes;
245 * Handles the start element of an XML resource
247 * @param $resource XML parser resource (currently ignored)
248 * @param $element The element we shall handle
249 * @param $attributes All attributes
251 * @throws InvalidXmlNodeException If an unknown/invalid XML node name was found
253 public function startElement ($resource, $element, array $attributes) {
254 // Initial method name which will never be called...
255 $methodName = 'initMenu';
257 // Make the element name lower-case
258 $element = strtolower($element);
260 // Is the element a main node?
261 //* DEBUG: */ echo "START: >".$element."<<br />\n";
262 if (in_array($element, $this->getMainNodes())) {
263 // Okay, main node found!
264 $methodName = 'start' . self::convertToClassName($element);
267 $this->setCurrMainNode($element);
268 } elseif (in_array($element, $this->getSubNodes())) {
270 $methodName = 'start' . self::convertToClassName($element);
271 } elseif ($element != 'menu') {
272 // Invalid node name found
273 throw new InvalidXmlNodeException(array($this, $element, $attributes), XmlParser::EXCEPTION_XML_NODE_UNKNOWN);
277 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
278 call_user_func_array(array($this, $methodName), $attributes);
282 * Ends the main or sub node by sending out the gathered data
284 * @param $resource An XML resource pointer (currently ignored)
285 * @param $nodeName Name of the node we want to finish
287 * @throws XmlNodeMismatchException If current main node mismatches the closing one
289 public function finishElement ($resource, $nodeName) {
290 // Make all lower-case
291 $nodeName = strtolower($nodeName);
293 // Does this match with current main node?
294 //* DEBUG: */ echo "END: >".$nodeName."<<br />\n";
295 if (($nodeName != $this->getCurrMainNode()) && (in_array($nodeName, $this->getMainNodes()))) {
297 throw new XmlNodeMismatchException (array($this, $nodeName, $this->getCurrMainNode()), XmlParser::EXCEPTION_XML_NODE_MISMATCH);
300 // Construct method name
301 $methodName = 'finish' . self::convertToClassName($nodeName);
303 // Call the corresponding method
304 //* DEBUG: */ echo "call: ".$methodName."<br />\n";
305 call_user_func_array(array($this, $methodName), array());
311 * @param $resource XML parser resource (currently ignored)
312 * @param $characters Characters to handle
314 * @todo Find something useful with this!
316 public function characterHandler ($resource, $characters) {
317 // Trim all spaces away
318 $characters = trim($characters);
320 // Is this string empty?
321 if (empty($characters)) {
322 // Then skip it silently
326 // Assign the found characters to variable and use the last entry from
328 parent::assignVariable($this->getStackInstance()->getNamed('current_node'), $characters);
332 * Handles the template dependency for given node
334 * @param $node The node we should load a dependency template
335 * @param $templateDependency A template to load to satisfy dependencies
338 private function handleTemplateDependency ($node, $templateDependency) {
339 // Is the template dependency set?
340 if ((!empty($templateDependency)) && (!isset($this->dependencyContent[$node]))) {
341 // Get a temporay menu template instance
342 $templateInstance = ObjectFactory::createObjectByConfiguredName('menu_template_class', array($this->getMenuInstance()));
345 $templateInstance->loadMenuTemplate($templateDependency);
347 // Parse the XML content
348 $templateInstance->renderXmlContent();
350 // Save the parsed raw content in our dependency array
351 $this->dependencyContent[$node] = $templateInstance->getRawTemplateData();
356 * Intializes the menu
358 * @param $templateDependency A template to load to satisfy dependencies
360 * @todo Add cache creation here
362 private function initMenu ($templateDependency = '') {
363 // Get web template engine
364 $this->setTemplateInstance(ObjectFactory::createObjectByConfiguredName('html_template_class', array($this->getApplicationInstance())));
366 // Handle the dependency template
367 $this->handleTemplateDependency('menu', $templateDependency);
369 // Push the node name on the stacker
370 $this->getStackInstance()->pushNamed('current_node', 'menu');
374 * Starts the menu entries
376 * @param $templateDependency A template to load to satisfy dependencies
379 private function startEntryList () {
380 // Push the node name on the stacker
381 $this->getStackInstance()->pushNamed('current_node', 'entry-list');
385 * Starts the menu block header
389 private function startBlockHeader () {
390 // Push the node name on the stacker
391 $this->getStackInstance()->pushNamed('current_node', 'block-header');
395 * Starts the menu block footer
399 private function startBlockFooter () {
400 // Push the node name on the stacker
401 $this->getStackInstance()->pushNamed('current_node', 'block-footer');
405 * Starts the menu property 'block-list'
409 private function startBlockList () {
410 // Push the node name on the stacker
411 $this->getStackInstance()->pushNamed('current_node', 'block-list');
415 * Starts the menu property 'block'
419 private function startBlock () {
420 // Push the node name on the stacker
421 $this->getStackInstance()->pushNamed('current_node', 'block');
425 * Starts the menu property 'title'
429 private function startTitle () {
430 // Push the node name on the stacker
431 $this->getStackInstance()->pushNamed('current_node', 'title');
435 * Starts the menu property 'title-id'
439 private function startTitleId () {
440 // Push the node name on the stacker
441 $this->getStackInstance()->pushNamed('current_node', 'title-id');
445 * Starts the menu property 'title-class'
449 private function startTitleClass () {
450 // Push the node name on the stacker
451 $this->getStackInstance()->pushNamed('current_node', 'title-class');
455 * Starts the menu property 'title-text'
459 private function startTitleText () {
460 // Push the node name on the stacker
461 $this->getStackInstance()->pushNamed('current_node', 'title-text');
465 * Starts the menu property 'entry'
469 private function startEntry () {
470 // Push the node name on the stacker
471 $this->getStackInstance()->pushNamed('current_node', 'entry');
475 * Starts the menu property 'entry-id'
479 private function startEntryId () {
480 // Push the node name on the stacker
481 $this->getStackInstance()->pushNamed('current_node', 'entry-id');
485 * Starts the menu property 'anchor'
489 private function startAnchor () {
490 // Push the node name on the stacker
491 $this->getStackInstance()->pushNamed('current_node', 'anchor');
495 * Starts the menu property 'anchor-id'
499 private function startAnchorId () {
500 // Push the node name on the stacker
501 $this->getStackInstance()->pushNamed('current_node', 'anchor-id');
505 * Starts the menu property 'anchor-text'
509 private function startAnchorText () {
510 // Push the node name on the stacker
511 $this->getStackInstance()->pushNamed('current_node', 'anchor-text');
515 * Starts the menu property 'anchor-title'
519 private function startAnchorTitle () {
520 // Push the node name on the stacker
521 $this->getStackInstance()->pushNamed('current_node', 'anchor-title');
525 * Starts the menu property 'anchor-href'
529 private function startAnchorHref () {
530 // Push the node name on the stacker
531 $this->getStackInstance()->pushNamed('current_node', 'anchor-href');
535 * Starts the menu property 'footer-id'
539 private function startFooterId () {
540 // Push the node name on the stacker
541 $this->getStackInstance()->pushNamed('current_node', 'footer-id');
545 * Starts the menu property 'footer-class'
549 private function startFooterClass () {
550 // Push the node name on the stacker
551 $this->getStackInstance()->pushNamed('current_node', 'footer-class');
555 * Starts the menu property 'footer-text'
559 private function startFooterText () {
560 // Push the node name on the stacker
561 $this->getStackInstance()->pushNamed('current_node', 'footer-text');
565 * Finishes the title node by added another template to the menu
569 private function finishTitle () {
570 // Pop the last entry
571 $this->getStackInstance()->popNamed('current_node');
575 * Finishes the title-id node by
579 private function finishTitleId () {
580 // Pop the last entry
581 $this->getStackInstance()->popNamed('current_node');
585 * Finishes the title-class node
589 private function finishTitleClass () {
590 // Pop the last entry
591 $this->getStackInstance()->popNamed('current_node');
595 * Finishes the title-class node
599 private function finishTitleText () {
600 // Pop the last entry
601 $this->getStackInstance()->popNamed('current_node');
605 * Finishes the footer-text node
609 private function finishFooterText () {
610 // Pop the last entry
611 $this->getStackInstance()->popNamed('current_node');
615 * Finishes the footer-class node
619 private function finishFooterClass () {
620 // Pop the last entry
621 $this->getStackInstance()->popNamed('current_node');
625 * Finishes the footer-id node
629 private function finishFooterId () {
630 // Pop the last entry
631 $this->getStackInstance()->popNamed('current_node');
635 * Finishes the anchor-href node
639 private function finishAnchorHref () {
640 // Pop the last entry
641 $this->getStackInstance()->popNamed('current_node');
645 * Finishes the anchor-title node
649 private function finishAnchorTitle () {
650 // Pop the last entry
651 $this->getStackInstance()->popNamed('current_node');
655 * Finishes the anchor-text node
659 private function finishAnchorText () {
660 // Pop the last entry
661 $this->getStackInstance()->popNamed('current_node');
665 * Finishes the anchor-id node
669 private function finishAnchorId () {
670 // Pop the last entry
671 $this->getStackInstance()->popNamed('current_node');
675 * Finishes the anchor node
679 private function finishAnchor () {
680 // Pop the last entry
681 $this->getStackInstance()->popNamed('current_node');
685 * Finishes the entry-id node
689 private function finishEntryId () {
690 // Pop the last entry
691 $this->getStackInstance()->popNamed('current_node');
695 * Finishes the entry node
699 private function finishEntry () {
700 // Pop the last entry
701 $this->getStackInstance()->popNamed('current_node');
703 // Render this menu entry
704 $this->renderMenuEntry();
708 * Finishes the block node
712 private function finishBlock () {
713 // Pop the last entry
714 $this->getStackInstance()->popNamed('current_node');
716 // Render this menu block
717 $this->renderMenuBlock();
721 * Finishes the block-list node
725 private function finishBlockList () {
726 // Pop the last entry
727 $this->getStackInstance()->popNamed('current_node');
731 * Finishes the menu entries
735 private function finishEntryList () {
736 // Pop the last entry
737 $this->getStackInstance()->popNamed('current_node');
741 * Finishes the menu block header
745 private function finishBlockHeader () {
746 // Pop the last entry
747 $this->getStackInstance()->popNamed('current_node');
751 * Finishes the menu block footer
755 private function finishBlockFooter () {
756 // Pop the last entry
757 $this->getStackInstance()->popNamed('current_node');
765 private function finishMenu () {
766 // Pop the last entry
767 $this->getStackInstance()->popNamed('current_node');
771 * Renders this menu entry, as every block all variables got overwritten
772 * with data from next entry.
776 private function renderMenuEntry () {
777 // Prepare template engine
778 $templateInstance = $this->prepareTemplateInstance();
780 // Load menu entry template
781 $templateInstance->loadCodeTemplate('menu_entry');
783 // Copy all variables over to it
784 foreach ($this->menuEntryVariables as $variableName) {
786 $variableValue = $this->readVariable($variableName);
788 // Is the key 'anchor-href'?
789 if ($variableName == 'anchor-href') {
790 // Expand variable with URL then
791 $variableValue = '{?base_url?}/' . $variableValue;
794 // ... into the instance
795 $templateInstance->assignVariable($variableName, $variableValue);
798 // Compile template + variables
799 $templateInstance->compileTemplate();
800 $templateInstance->compileVariables();
803 $this->menuEntries[$this->readVariable('entry_id')] = $templateInstance->getRawTemplateData();
807 * Renders this menu block, as next block all data is overwritten with
812 private function renderMenuBlock () {
813 // Init block content
814 $blockContent = implode('', $this->menuEntries);
816 // Prepare template engine
817 $templateInstance = $this->prepareTemplateInstance();
819 // Load menu entry template
820 $templateInstance->loadCodeTemplate('menu_block');
822 // Copy all variables over to it
823 foreach ($this->menuBlockVariables as $variableName) {
825 $variableValue = $this->readVariable($variableName);
827 // ... into the instance
828 $templateInstance->assignVariable($variableName, $variableValue);
831 // Assign block content
832 $templateInstance->assignVariable('block_content', $blockContent);
834 // Compile template + variables
835 $templateInstance->compileTemplate();
836 $templateInstance->compileVariables();
839 array_push($this->menuBlocks, $templateInstance->getRawTemplateData());
841 // Reset rendered menu entries array
842 $this->menuEntries = array();
846 * "Getter" for menu content
848 * @return $menuContent Returned menu content
850 public function getMenuContent () {
851 // Implode menuBlocks
852 $menuContent = implode('', $this->menuBlocks);
855 $this->menuBlocks = array();
862 * Getter for menu cache file (FQFN)
864 * @return $fqfn Full-qualified file name of the menu cache
866 public function getMenuCacheFqfn () {
867 // Get the FQFN ready
868 $fqfn = sprintf('%s%s%s/%s.%s',
869 $this->getConfigInstance()->getConfigEntry('base_path'),
870 $this->getGenericBasePath(),
873 $this->getMenuInstance()->getMenuName() . ':' .
874 $this->__toString() . ':' .
875 $this->getMenuInstance()->__toString()
877 $this->getMenuInstance()->getMenuType()