PageRenderTime 62ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/Opl/Opt/Instruction/BaseSection.php

https://bitbucket.org/kandsten/hitta.sverok.se
PHP | 615 lines | 386 code | 47 blank | 182 comment | 52 complexity | 61b3e9cbd80430d8e0bb4b01b9a80f64 MD5 | raw file
Possible License(s): GPL-3.0, MIT
  1. <?php
  2. /*
  3. * OPEN POWER LIBS <http://www.invenzzia.org>
  4. *
  5. * This file is subject to the new BSD license that is bundled
  6. * with this package in the file LICENSE. It is also available through
  7. * WWW at this URL: <http://www.invenzzia.org/license/new-bsd>
  8. *
  9. * Copyright (c) Invenzzia Group <http://www.invenzzia.org>
  10. * and other contributors. See website for details.
  11. *
  12. * $Id: BaseSection.php 231 2009-09-19 07:13:14Z zyxist $
  13. */
  14. abstract class Opt_Instruction_BaseSection extends Opt_Instruction_Loop
  15. {
  16. static private $_sections = array();
  17. static private $_stack;
  18. /**
  19. * Initializes the section processor.
  20. *
  21. * @param Opt_Compiler_Class $compiler The compiler object
  22. */
  23. public function __construct($compiler)
  24. {
  25. parent::__construct($compiler);
  26. if(!is_object(self::$_stack))
  27. {
  28. self::$_stack = new SplStack;
  29. }
  30. } // end __construct();
  31. /**
  32. * Resets the processor state.
  33. */
  34. public function reset()
  35. {
  36. if(sizeof(self::$_sections) > 0)
  37. {
  38. self::$_stack = new SplStack;
  39. self::$_sections = array();
  40. }
  41. } // end reset();
  42. /**
  43. * Returns the information record about the specified section.
  44. *
  45. * @static
  46. * @param String $name Section ID
  47. * @return Array The information record or NULL, if the section is not found.
  48. */
  49. static public function getSection($name)
  50. {
  51. if(!isset(self::$_sections[$name]))
  52. {
  53. return NULL;
  54. }
  55. return self::$_sections[$name];
  56. } // end getSection();
  57. /**
  58. * Returns the currently parsed section record.
  59. *
  60. * @static
  61. * @return Array The information record or NULL, if no sections are active.
  62. */
  63. static public function getLastSection()
  64. {
  65. return self::getSection(self::$_stack->top());
  66. } // end getLastSection();
  67. /**
  68. * Returns the number of active sections.
  69. *
  70. * @static
  71. * @return Int The number of sections on the stack.
  72. */
  73. static public function countSections()
  74. {
  75. return self::$_stack->count();
  76. } // end countSections();
  77. /**
  78. * Creates a new section record, using the information from the specified node.
  79. * If the node is not a valid OPT instruction, the method scans the ancestors
  80. * to find a free opt:show node.
  81. *
  82. * The method can also initialize the attributed section, if we provide the
  83. * opt:section attribute object as the second argument.
  84. *
  85. * The method initializes also the neighbourhood of the section by parsing the
  86. * opt:show tag, if necessary and creating the enter condition. Note that it
  87. * does not start the section - the record is neither put onto the section stack
  88. * nor opt:use integration is not made. You have to use _sectionStart() in order
  89. * to fully initialize the section.
  90. *
  91. * @param Opt_Xml_Element $node
  92. * @param Opt_Xml_Attribute $attr optional
  93. * @param Array $extraAttributes optional
  94. * @return Array The section record.
  95. */
  96. protected function _sectionCreate(Opt_Xml_Element $node, $attr = NULL, $extraAttributes = NULL)
  97. {
  98. /* First, we need to determine, whether the section is associated with
  99. * opt:show. In case of tag sections, this is done only if the node
  100. * does not contain any section attributes.
  101. */
  102. $show = NULL;
  103. if($attr instanceof Opt_Xml_Attribute)
  104. {
  105. $show = $this->_findShowNode($node, $attr->getValue());
  106. if(!is_null($show))
  107. {
  108. // In this case we can obtain the attributes from opt:show.
  109. $section = $this->_extractSectionAttributes($show, $extraAttributes);
  110. $section['show'] = $show;
  111. $section['node'] = $node;
  112. $section['attribute'] = $attr;
  113. }
  114. else
  115. {
  116. // Generate a default section record, using the $attr value and
  117. // optionally checking for the "separator" attribute in the current node.
  118. $_params = array(
  119. 'separator' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
  120. );
  121. $this->_extractAttributes($node, $_params);
  122. $section = array(
  123. 'name' => $attr->getValue(),
  124. 'order' => 'asc',
  125. 'parent' => NULL,
  126. 'datasource' => NULL,
  127. 'display' => NULL,
  128. 'separator' => $_params['separator'],
  129. 'show' => null,
  130. 'node' => $node,
  131. 'attribute' => $attr
  132. );
  133. }
  134. }
  135. else
  136. {
  137. if(is_null($node->getAttribute('name')))
  138. {
  139. // We must look for opt:show
  140. $show = $this->_findShowNode($node);
  141. if(is_null($show))
  142. {
  143. throw new Opt_AttributeNotDefined_Exception('name', $node->getXmlName());
  144. }
  145. $section = $this->_extractSectionAttributes($show, $extraAttributes);
  146. $section['show'] = $show;
  147. $section['node'] = $node;
  148. $section['attribute'] = null;
  149. }
  150. else
  151. {
  152. $section = $this->_extractSectionAttributes($node, $extraAttributes);
  153. $section['show'] = null;
  154. $section['node'] = $node;
  155. $section['attribute'] = null;
  156. }
  157. }
  158. $this->_validateSection($section);
  159. if(is_null($section['show']))
  160. {
  161. $this->_createShowCondition($node, $section);
  162. }
  163. elseif(is_null($section['show']->get('priv:initialized')))
  164. {
  165. $this->_createShowCondition($section['show'], $section);
  166. }
  167. // A faster way to obtain the section name associated with this section.
  168. $node->set('priv:section', $section['name']);
  169. return $section;
  170. } // end _sectionCreate();
  171. /**
  172. * Starts the section by putting its record on a section stack.
  173. *
  174. * @param Array $section The section record.
  175. */
  176. protected function _sectionStart(Array &$section)
  177. {
  178. self::_addSection($section);
  179. if(!is_null($section['node']->get('call:use')))
  180. {
  181. $this->_compiler->setConversion('##simplevar_'.$section['node']->get('call:use'), $section['name']);
  182. }
  183. // Populate the debug console.
  184. if($this->_tpl->debugConsole)
  185. {
  186. if(isset($section['datasource']))
  187. {
  188. $parent = '<em>Datasource</em>';
  189. }
  190. elseif(!is_null($section['parent']))
  191. {
  192. $parent = $section['parent'];
  193. }
  194. else
  195. {
  196. $parent = '-';
  197. }
  198. Opt_Support::addSection($section['name'], $parent, (string)$section['format'], $section['node']->getXmlName());
  199. }
  200. } // end _sectionStart();
  201. /**
  202. * Finalizes the section associated with the specified XML node. If the
  203. * node does not contain any valid section information, it generates
  204. * an exception.
  205. *
  206. * @param Opt_Xml_Element $node The section node
  207. */
  208. protected function _sectionEnd(Opt_Xml_Element $node)
  209. {
  210. $section = $node->get('priv:section');
  211. if(!is_array($section))
  212. {
  213. $section = self::getSection($section);
  214. }
  215. if(!is_null($node->get('call:use')))
  216. {
  217. $this->_compiler->unsetConversion('##simplevar_'.$node->get('priv:section'));
  218. }
  219. self::_removeSection($section['name']);
  220. } // end _sectionEnd();
  221. /**
  222. * Adds the show condition PHP code to the specified node, using the
  223. * information from the section record.
  224. *
  225. * @internal
  226. * @param Opt_Xml_Element $node The node, where to add the show condition.
  227. * @param Array &$section The section info record
  228. */
  229. private function _createShowCondition(Opt_Xml_Element $node, &$section)
  230. {
  231. // First, try to check for the call:use tag variable.
  232. if($node->get('call:use') !== NULL)
  233. {
  234. $section['node']->set('call:use', $node->get('call:use'));
  235. }
  236. // Deal with the data formats
  237. $format = $section['format'];
  238. $format->assign('section', $section);
  239. $request = $format->property('section:anyRequests');
  240. if(!is_null($request))
  241. {
  242. // Send the requested data, if the data format needs any.
  243. switch($request)
  244. {
  245. case 'ancestorNames':
  246. self::$_stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
  247. $list = array();
  248. $parent = $section['parent'];
  249. foreach(self::$_stack as $up)
  250. {
  251. if($up == $parent)
  252. {
  253. $parent = self::$_sections[$up]['parent'];
  254. $list[] = self::$_sections[$up]['name'];
  255. }
  256. }
  257. $format->assign('requestedData', array_reverse($list));
  258. break;
  259. case 'ancestorNumbers':
  260. self::$_stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
  261. $list = array();
  262. $parent = $section['parent'];
  263. $iteration = self::$_stack->count();
  264. foreach(self::$_stack as $up)
  265. {
  266. if($up == $parent)
  267. {
  268. $parent = self::$_sections[$up]['parent'];
  269. $list[] = $iteration;
  270. }
  271. $iteration--;
  272. }
  273. $format->assign('requestedData', array_reverse($list));
  274. break;
  275. }
  276. }
  277. $code = $format->get('section:init');
  278. if(is_null($section['display']))
  279. {
  280. $code .= ' if('.$format->get('section:isNotEmpty').'){ ';
  281. }
  282. else
  283. {
  284. $code .= ' if('.$format->get('section:isNotEmpty').' && '.$section['display'].'){ ';
  285. }
  286. $code .= $format->get('section:started');
  287. $node->addBefore(Opt_Xml_Buffer::TAG_BEFORE, $code);
  288. $code = $format->get('section:finished').' } '.$format->get('section:done');
  289. $node->addAfter(Opt_Xml_Buffer::TAG_AFTER, $code);
  290. $node->set('priv:initialized', true);
  291. } // end _createShowCondition();
  292. /**
  293. * Finds the nearest free opt:show node in the ascentors of the specified node.
  294. *
  295. * @internal
  296. * @param Opt_Xml_Element $item The current node.
  297. * @param String $name optional The name that opt:show must match.
  298. * @return Opt_Xml_Element The opt:show node or NULL if not found.
  299. */
  300. private function _findShowNode(Opt_Xml_Element $item, $name = null)
  301. {
  302. if(!is_null($name))
  303. {
  304. // The section names must also match!
  305. while(!is_null($item = $item->getParent()))
  306. {
  307. if($item instanceof Opt_Xml_Element)
  308. {
  309. if($item->getXmlName() == 'opt:show')
  310. {
  311. $nameAttr = $item->getAttribute('name');
  312. if(!is_null($nameAttr) && $nameAttr->getValue() == $name)
  313. {
  314. return $item;
  315. }
  316. }
  317. }
  318. }
  319. }
  320. else
  321. {
  322. // Here we do not need to check, whether the name in opt:show matches the argument.
  323. while(!is_null($item = $item->getParent()))
  324. {
  325. if($item instanceof Opt_Xml_Element)
  326. {
  327. if($item->getXmlName() == 'opt:show')
  328. {
  329. return $item;
  330. }
  331. }
  332. }
  333. }
  334. return NULL;
  335. } // end _findShowNode();
  336. /**
  337. * The section parameter parsing is used in several places, so the
  338. * code was moved to a separate method.
  339. *
  340. * @internal
  341. * @param Opt_Xml_Element $node Parse the attributes from this node.
  342. * @param Array $extraAttributes=NULL Extra section attributes
  343. * @return Array The extracted attributes.
  344. */
  345. private function _extractSectionAttributes(Opt_Xml_Element $node, $extraAttributes = NULL)
  346. {
  347. $params = array(
  348. 'name' => array(0 => self::REQUIRED, self::ID),
  349. 'parent' => array(0 => self::OPTIONAL, self::ID_EMP, NULL),
  350. 'datasource' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
  351. 'order' => array(0 => self::OPTIONAL, self::ID, 'asc'),
  352. 'display' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
  353. 'separator' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
  354. );
  355. // The instruction may add some extra attributes.
  356. if(!is_null($extraAttributes))
  357. {
  358. $params = array_merge($params, $extraAttributes);
  359. }
  360. $this->_extractAttributes($node, $params);
  361. return $params;
  362. } // end _extractSectionAttributes();
  363. /**
  364. * Validates the section record, determines the section parent and
  365. * the data format.
  366. *
  367. * @internal
  368. * @param Array &$section The section record.
  369. */
  370. private function _validateSection(Array &$section)
  371. {
  372. // Verify the value of the "order" attribute.
  373. if($section['order'] != 'asc' && $section['order'] != 'desc')
  374. {
  375. throw new Opt_InvalidAttributeType_Exception('order', $node->getXmlName(), '"asc" or "desc"');
  376. }
  377. // Determine the parent of the specified section.
  378. // if the attribute is not set, the default behaviour is to find the nearest
  379. // top-level and active section and link to it. Otherwise we must check if
  380. // the chosen section exists and is active.
  381. // Note that "parent" is ignored when we set "datasource"
  382. if(is_null($section['parent']))
  383. {
  384. if(self::$_stack->count() > 0)
  385. {
  386. $section['parent'] = self::$_stack->top();
  387. }
  388. }
  389. elseif($section['parent'] != '')
  390. {
  391. if(is_null(self::getSection($section['parent'])))
  392. {
  393. $exception = new Opt_SectionNotFound_Exception('parent', $section['parent']);
  394. $sections = array();
  395. self::$_stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
  396. foreach(self::$_stack as $up)
  397. {
  398. $sections[] = $up;
  399. }
  400. $exception->setData($sections);
  401. throw $exception;
  402. }
  403. }
  404. else
  405. {
  406. // For the situation, if we had 'parent=""' in the template.
  407. $section['parent'] = null;
  408. }
  409. $section['nesting'] = self::countSections() + 1;
  410. // Now we need to obtain the information about the data format.
  411. $section['format'] = $this->_compiler->getFormat($section['name']);
  412. if(!$section['format']->supports('section'))
  413. {
  414. throw new Opt_FormatNotSupported_Exception($section['format']->getName(), 'section');
  415. }
  416. } // end _validateSection();
  417. /**
  418. * Adds the new section record to the stack.
  419. *
  420. * @static
  421. * @internal
  422. * @param Array $info The section record.
  423. */
  424. static private function _addSection(Array $info)
  425. {
  426. if(isset(self::$_sections[$info['name']]))
  427. {
  428. throw new Opt_SectionExists_Exception('adding section', $info['name']);
  429. }
  430. self::$_sections[$info['name']] = $info;
  431. self::$_stack->push($info['name']);
  432. } // end _addSectionInfo();
  433. /**
  434. * Removes the specified section from the stack. The name
  435. * is provided to check, if the order of the closing is
  436. * valid.
  437. *
  438. * @static
  439. * @internal
  440. * @param String $name The section name.
  441. */
  442. static private function _removeSection($name)
  443. {
  444. if(self::$_stack->count() == 0)
  445. {
  446. throw new Opt_ObjectNotExists_Exception('section', $name);
  447. }
  448. $name2 = self::$_stack->pop();
  449. if($name != $name2)
  450. {
  451. throw new Opl_Debug_Generic_Exception('OPT: Invalid section name thrown from the stack. Expected: '.$name.'; Actual: '.$name2);
  452. }
  453. unset(self::$_sections[$name]);
  454. } // end _removeSection;
  455. /**
  456. * A dummy opt:show processor that actually does nothing, because
  457. * it must wait for the right section instruction. If such instruction
  458. * will not appear, the node will be parsed in the postprocessing.
  459. *
  460. * @internal
  461. * @param Opt_Xml_Node $node The node
  462. */
  463. protected function _processShow(Opt_Xml_Node $node)
  464. {
  465. $node->set('postprocess', true);
  466. $this->_sortSectionContents($node, 'opt', 'showelse');
  467. $this->_process($node);
  468. } // end _processShow();
  469. /**
  470. * Finalizes the opt:show attribute. If there was no section in opt:show,
  471. * it initializes a section for a moment just to generate the condition,
  472. * but does not add it to the stack.
  473. *
  474. * @param Opt_Xml_Node $node The node.
  475. */
  476. protected function _postprocessShow(Opt_Xml_Node $node)
  477. {
  478. if(!is_null($node->get('priv:initialized')))
  479. {
  480. return;
  481. }
  482. $section = $this->_extractSectionAttributes($node, null);
  483. $section['show'] = $node;
  484. $section['node'] = null;
  485. $section['attribute'] = null;
  486. $this->_validateSection($section);
  487. $this->_createShowCondition($node, $section);
  488. } // end _postprocessShow();
  489. /**
  490. * An utility method that cleans the contents of the section node, by
  491. * moving the alternative section (opt:sectionelse etc. tags) code to the end
  492. * of the children list.
  493. *
  494. * In the parameters, we must specify the name and the namespace of the
  495. * tags that will be treated as the alternative content tags.
  496. *
  497. * @param Opt_Xml_Element $node The section node
  498. * @param String $ns The namespace
  499. * @param String $name The alternative section content tag name
  500. */
  501. protected function _sortSectionContents(Opt_Xml_Element $node, $ns, $name)
  502. {
  503. $else = $node->getElementsByTagNameNS($ns, $name, false);
  504. if(sizeof($else) == 1)
  505. {
  506. if(!$node->hasAttributes())
  507. {
  508. throw new Opt_InstructionTooManyItems_Exception($ns.':'.$name, $node->getXmlName(), 'Zero');
  509. }
  510. $node->bringToEnd($else[0]);
  511. }
  512. elseif(sizeof($else) > 1)
  513. {
  514. throw new Opt_InstructionTooManyItems_Exception($ns.':'.$name, $node->getXmlName(), 'Zero or one');
  515. }
  516. } // end _locateElse();
  517. /**
  518. * Processes the system variable $sys for the sections.
  519. *
  520. * @param Array $opt The system variable call splitted into separate identifiers.
  521. * @return String The output PHP code.
  522. */
  523. public function processSystemVar($opt)
  524. {
  525. if(sizeof($opt) < 4)
  526. {
  527. throw new Opt_SysVariableLength_Exception('$'.implode('.',$opt), 'short');
  528. }
  529. // Determine the section
  530. $section = self::getSection($opt[2]);
  531. if(is_null($section))
  532. {
  533. throw new Opt_SectionNotFound_Exception('OPT variable $'.implode('.',$opt), $opt[2]);
  534. }
  535. switch($opt[3])
  536. {
  537. case 'count':
  538. return $section['format']->get('section:count');
  539. case 'iterator':
  540. return $section['format']->get('section:iterator');
  541. case 'size':
  542. return $section['format']->get('section:size');
  543. case 'first':
  544. return $section['format']->get('section:isFirst');
  545. case 'last':
  546. return $section['format']->get('section:isLast');
  547. case 'extreme':
  548. return $section['format']->get('section:isExtreme');
  549. default:
  550. $result = $this->_processSystemVar($opt);
  551. if(is_null($result))
  552. {
  553. throw new Opt_SysVariableUnknown_Exception('$'.implode('.',$opt));
  554. }
  555. return $result;
  556. }
  557. } // end processSystemVar();
  558. /**
  559. * Allows the sections to handle specific uses of $sys special variable.
  560. *
  561. * @param Array $opt The system variable call splitted into separate identifiers.
  562. * @return String The output PHP code.
  563. */
  564. protected function _processSystemVar($opt)
  565. {
  566. return NULL;
  567. } // end _processSystemVar();
  568. } // end Opt_Instruction_BaseSection;