PageRenderTime 23ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/koowa/template/abstract.php

https://bitbucket.org/organicdevelopment/nooku-framework
PHP | 559 lines | 247 code | 79 blank | 233 comment | 36 complexity | 63eee2503a656b0550fed8917365b356 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * @version $Id$
  4. * @package Koowa_Template
  5. * @copyright Copyright (C) 2007 - 2012 Johan Janssens. All rights reserved.
  6. * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html>
  7. * @link http://www.nooku.org
  8. */
  9. /**
  10. * Abstract Template class
  11. *
  12. * @author Johan Janssens <johan@nooku.org>
  13. * @package Koowa_Template
  14. */
  15. abstract class KTemplateAbstract extends KObject
  16. {
  17. /**
  18. * The template path
  19. *
  20. * @var string
  21. */
  22. protected $_path;
  23. /**
  24. * The template data
  25. *
  26. * @var array
  27. */
  28. protected $_data = array();
  29. /**
  30. * The template contents
  31. *
  32. * @var string
  33. */
  34. protected $_contents = '';
  35. /**
  36. * The set of template filters for templates
  37. *
  38. * @var array
  39. */
  40. protected $_filters = array();
  41. /**
  42. * View object or identifier (com://APP/COMPONENT.view.NAME.FORMAT)
  43. *
  44. * @var string|object
  45. */
  46. protected $_view;
  47. /**
  48. * The template stack object
  49. *
  50. * @var KTemplateStack
  51. */
  52. protected $_stack;
  53. /**
  54. * Template errors
  55. *
  56. * @var array
  57. */
  58. private static $_errors = array(
  59. 1 => 'Fatal Error',
  60. 2 => 'Warning',
  61. 4 => 'Parse Error',
  62. 8 => 'Notice',
  63. 64 => 'Compile Error',
  64. 256 => 'User Error',
  65. 512 => 'User Warning',
  66. 2048 => 'Strict',
  67. 4096 => 'Recoverable Error'
  68. );
  69. /**
  70. * Constructor
  71. *
  72. * Prevent creating instances of this class by making the contructor private
  73. *
  74. * @param object An optional KConfig object with configuration options
  75. */
  76. public function __construct(KConfig $config)
  77. {
  78. parent::__construct($config);
  79. // Set the view indentifier
  80. $this->_view = $config->view;
  81. // Set the template stack object
  82. $this->_stack = $config->stack;
  83. //Register the template stream wrapper
  84. KTemplateStream::register();
  85. //Set shutdown function to handle sandbox errors
  86. register_shutdown_function(array($this, '__destroy'));
  87. // Mixin a command chain
  88. $this->mixin(new KMixinCommand($config->append(array('mixer' => $this))));
  89. }
  90. /**
  91. * Destructor
  92. *
  93. * Hanlde sandbox shutdown. Clean all output buffers and display the latest error
  94. * if an error is found.
  95. *
  96. * @return bool
  97. */
  98. public function __destroy()
  99. {
  100. if(!$this->getStack()->isEmpty())
  101. {
  102. if($error = error_get_last())
  103. {
  104. if($error['type'] === E_ERROR || $error['type'] === E_PARSE || $error['type'] === E_COMPILE_ERROR)
  105. {
  106. while(@ob_get_clean());
  107. $this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
  108. }
  109. }
  110. }
  111. }
  112. /**
  113. * Initializes the options for the object
  114. *
  115. * Called from {@link __construct()} as a first step of object instantiation.
  116. *
  117. * @param object An optional KConfig object with configuration options.
  118. * @return void
  119. */
  120. protected function _initialize(KConfig $config)
  121. {
  122. $config->append(array(
  123. 'stack' => $this->getService('koowa:template.stack'),
  124. 'view' => null,
  125. 'command_chain' => $this->getService('koowa:command.chain'),
  126. 'dispatch_events' => false,
  127. 'enable_callbacks' => false,
  128. ));
  129. parent::_initialize($config);
  130. }
  131. /**
  132. * Get the template path
  133. *
  134. * @return string
  135. */
  136. public function getPath()
  137. {
  138. return $this->_path;
  139. }
  140. /**
  141. * Get the template data
  142. *
  143. * @return mixed
  144. */
  145. public function getData()
  146. {
  147. return $this->_data;
  148. }
  149. /**
  150. * Get the template object stack
  151. *
  152. * @return KTemplateStack
  153. */
  154. public function getStack()
  155. {
  156. return $this->_stack;
  157. }
  158. /**
  159. * Get the view object attached to the template
  160. *
  161. * @return KViewAbstract
  162. */
  163. public function getView()
  164. {
  165. if(!$this->_view instanceof KViewAbstract)
  166. {
  167. //Make sure we have a view identifier
  168. if(!($this->_view instanceof KServiceIdentifier)) {
  169. $this->setView($this->_view);
  170. }
  171. $this->_view = $this->getService($this->_view);
  172. }
  173. return $this->_view;
  174. }
  175. /**
  176. * Method to set a view object attached to the controller
  177. *
  178. * @param mixed An object that implements KObjectServiceable, KServiceIdentifier object
  179. * or valid identifier string
  180. * @throws KDatabaseRowsetException If the identifier is not a view identifier
  181. * @return KControllerAbstract
  182. */
  183. public function setView($view)
  184. {
  185. if(!($view instanceof KViewAbstract))
  186. {
  187. if(is_string($view) && strpos($view, '.') === false )
  188. {
  189. $identifier = clone $this->getIdentifier();
  190. $identifier->path = array('view', $view);
  191. $identifier->name = 'html';
  192. }
  193. else $identifier = $this->getIdentifier($view);
  194. if($identifier->path[0] != 'view') {
  195. throw new KTemplateException('Identifier: '.$identifier.' is not a view identifier');
  196. }
  197. $view = $identifier;
  198. }
  199. $this->_view = $view;
  200. return $this;
  201. }
  202. /**
  203. * Load a template by identifier
  204. *
  205. * This functions only accepts full identifiers of the format
  206. * - com:[//application/]component.view.[.path].name
  207. *
  208. * @param string The template identifier
  209. * @param array An associative array of data to be extracted in local template scope
  210. * @param boolean If TRUE process the data using a tmpl stream. Default TRUE.
  211. * @return KTemplateAbstract
  212. */
  213. public function loadIdentifier($template, $data = array(), $process = true)
  214. {
  215. //Identify the template
  216. $identifier = $this->getIdentifier($template);
  217. // Find the template
  218. $file = $this->findFile($identifier->filepath);
  219. if ($file === false) {
  220. throw new KTemplateException('Template "'.$identifier->name.'" not found');
  221. }
  222. // Load the file
  223. $this->loadFile($file, $data, $process);
  224. return $this;
  225. }
  226. /**
  227. * Load a template by path
  228. *
  229. * @param string The template path
  230. * @param array An associative array of data to be extracted in local template scope
  231. * @param boolean If TRUE process the data using a tmpl stream. Default TRUE.
  232. * @return KTemplateAbstract
  233. */
  234. public function loadFile($file, $data = array(), $process = true)
  235. {
  236. // store the path
  237. $this->_path = $file;
  238. // get the file contents
  239. $contents = file_get_contents($file);
  240. // load the contents
  241. $this->loadString($contents, $data, $process);
  242. return $this;
  243. }
  244. /**
  245. * Load a template from a string
  246. *
  247. * @param string The template contents
  248. * @param array An associative array of data to be extracted in local template scope
  249. * @param boolean If TRUE process the data using a tmpl stream. Default TRUE.
  250. * @return KTemplateAbstract
  251. */
  252. public function loadString($string, $data = array(), $process = true)
  253. {
  254. $this->_contents = $string;
  255. // Merge the data
  256. $this->_data = array_merge((array) $this->_data, $data);
  257. // Process the data
  258. if($process == true) {
  259. $this->__sandbox();
  260. }
  261. return $this;
  262. }
  263. /**
  264. * Render the template
  265. *
  266. * This function passes the template throught write filter chain and returns the
  267. * result.
  268. *
  269. * @return string The rendered data
  270. */
  271. public function render()
  272. {
  273. $context = $this->getCommandContext();
  274. $context->data = $this->_contents;
  275. $result = $this->getCommandChain()->run(KTemplateFilter::MODE_WRITE, $context);
  276. return $context->data;
  277. }
  278. /**
  279. * Parse the template
  280. *
  281. * This function passes the template throught read filter chain and returns the
  282. * result.
  283. *
  284. * @return string The parsed data
  285. */
  286. public function parse()
  287. {
  288. $context = $this->getCommandContext();
  289. $context->data = $this->_contents;
  290. $result = $this->getCommandChain()->run(KTemplateFilter::MODE_READ, $context);
  291. return $context->data;
  292. }
  293. /**
  294. * Check if a filter exists
  295. *
  296. * @param string The name of the filter
  297. * @return boolean TRUE if the filter exists, FALSE otherwise
  298. */
  299. public function hasFilter($filter)
  300. {
  301. return isset($this->_filters[$filter]);
  302. }
  303. /**
  304. * Adds one or more filters for template transformation
  305. *
  306. * @param array Array of one or more behaviors to add.
  307. * @return KTemplate
  308. */
  309. public function addFilter($filters)
  310. {
  311. $filters = (array) KConfig::unbox($filters);
  312. foreach($filters as $filter)
  313. {
  314. if(!($filter instanceof KTemplateFilterInterface)) {
  315. $filter = $this->getFilter($filter);
  316. }
  317. //Enqueue the filter in the command chain
  318. $this->getCommandChain()->enqueue($filter);
  319. //Store the filter
  320. $this->_filters[$filter->getIdentifier()->name] = $filter;
  321. }
  322. return $this;
  323. }
  324. /**
  325. * Get a filter by identifier
  326. *
  327. * @return KTemplateFilterInterface
  328. */
  329. public function getFilter($filter)
  330. {
  331. //Create the complete identifier if a partial identifier was passed
  332. if(is_string($filter) && strpos($filter, '.') === false )
  333. {
  334. $identifier = clone $this->getIdentifier();
  335. $identifier->path = array('template', 'filter');
  336. $identifier->name = $filter;
  337. }
  338. else $identifier = KService::getIdentifier($filter);
  339. if (!isset($this->_filters[$identifier->name]))
  340. {
  341. $filter = KService::get($identifier);
  342. if(!($filter instanceof KTemplateFilterInterface)) {
  343. throw new KTemplateException("Template filter $identifier does not implement KTemplateFilterInterface");
  344. }
  345. }
  346. else $filter = $this->_filters[$identifier->name];
  347. return $filter;
  348. }
  349. /**
  350. * Searches for the file
  351. *
  352. * @param string The file path to look for.
  353. * @return mixed The full path and file name for the target file, or FALSE
  354. * if the file is not found
  355. */
  356. public function findFile($file)
  357. {
  358. $result = false;
  359. $path = dirname($file);
  360. // is the path based on a stream?
  361. if (strpos($path, '://') === false)
  362. {
  363. // not a stream, so do a realpath() to avoid directory
  364. // traversal attempts on the local file system.
  365. $path = realpath($path); // needed for substr() later
  366. $file = realpath($file);
  367. }
  368. // The substr() check added to make sure that the realpath()
  369. // results in a directory registered so that non-registered directores
  370. // are not accessible via directory traversal attempts.
  371. if (file_exists($file) && substr($file, 0, strlen($path)) == $path) {
  372. $result = $file;
  373. }
  374. // could not find the file in the set of paths
  375. return $result;
  376. }
  377. /**
  378. * Load a template helper
  379. *
  380. * This functions accepts a partial identifier, in the form of helper.function. If a partial
  381. * identifier is passed a full identifier will be created using the template identifier.
  382. *
  383. * @param string Name of the helper, dot separated including the helper function to call
  384. * @param array An optional associative array of configuration settings
  385. * @return string Helper output
  386. */
  387. public function renderHelper($identifier, $config = array())
  388. {
  389. //Get the function to call based on the $identifier
  390. $parts = explode('.', $identifier);
  391. $function = array_pop($parts);
  392. $helper = $this->getHelper(implode('.', $parts), $config);
  393. //Call the helper function
  394. if (!is_callable( array( $helper, $function ) )) {
  395. throw new KTemplateHelperException( get_class($helper).'::'.$function.' not supported.' );
  396. }
  397. return $helper->$function($config);
  398. }
  399. /**
  400. * Get a template helper
  401. *
  402. * @param mixed KServiceIdentifierInterface
  403. * @param array An optional associative array of configuration settings
  404. * @return KTemplateHelperInterface
  405. */
  406. public function getHelper($helper, $config = array())
  407. {
  408. //Create the complete identifier if a partial identifier was passed
  409. if(is_string($helper) && strpos($helper, '.') === false )
  410. {
  411. $identifier = clone $this->getIdentifier();
  412. $identifier->path = array('template','helper');
  413. $identifier->name = $helper;
  414. }
  415. else $identifier = $this->getIdentifier($helper);
  416. //Create the template helper
  417. $helper = $this->getService($identifier, array_merge($config, array('template' => $this)));
  418. //Check the helper interface
  419. if(!($helper instanceof KTemplateHelperInterface)) {
  420. throw new KTemplateHelperException("Template helper $identifier does not implement KTemplateHelperInterface");
  421. }
  422. return $helper;
  423. }
  424. /**
  425. * Handle template errors
  426. *
  427. * @return bool
  428. */
  429. public function handleError($code, $message, $file = '', $line = 0, $context = array())
  430. {
  431. if($file == 'tmpl://koowa:template.stack')
  432. {
  433. if(ini_get('display_errors')) {
  434. echo '<strong>'.self::$_errors[$code].'</strong>: '.$message.' in <strong>'.$this->_path.'</strong> on line <strong>'.$line.'</strong>';
  435. }
  436. if(ini_get('log_errors')) {
  437. error_log(sprintf('PHP %s: %s in %s on line %d', self::$_errors[$code], $message, $this->_path, $line));
  438. }
  439. return true;
  440. }
  441. return false;
  442. }
  443. /**
  444. * Process the template using a simple sandbox
  445. *
  446. * This function passes the template through the read filter chain before letting
  447. * the PHP parser executed it. The result is buffered.
  448. *
  449. * @param boolean If TRUE apply write filters. Default FALSE.
  450. * @return KTemplateAbstract
  451. */
  452. private function __sandbox()
  453. {
  454. set_error_handler(array($this, 'handleError'), E_WARNING | E_NOTICE);
  455. $this->getStack()->push(clone $this);
  456. //Extract the data in local scope
  457. extract($this->_data, EXTR_SKIP);
  458. // Capturing output into a buffer
  459. ob_start();
  460. include 'tmpl://'.$this->getStack()->getIdentifier();
  461. $this->_contents = ob_get_clean();
  462. $this->getStack()->pop();
  463. restore_error_handler();
  464. return $this;
  465. }
  466. /**
  467. * Renders the template and returns the result
  468. *
  469. * @return string
  470. */
  471. public function __toString()
  472. {
  473. try {
  474. $result = $this->_contents;
  475. } catch (Exception $e) {
  476. $result = $e->getMessage();
  477. }
  478. return $result;
  479. }
  480. }