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

/libs/Nette/Templating/Template.php

https://github.com/kaja47/autotweeter
PHP | 503 lines | 242 code | 118 blank | 143 comment | 42 complexity | c5abb2612f2a022095276c706fa9949c MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Templating;
  11. use Nette,
  12. Nette\Caching;
  13. /**
  14. * Template.
  15. *
  16. * @author David Grudl
  17. */
  18. class Template extends Nette\Object implements ITemplate
  19. {
  20. /** @var bool */
  21. public $warnOnUndefined = TRUE;
  22. /** @var array of function(Template $sender); Occurs before a template is compiled - implement to customize the filters */
  23. public $onPrepareFilters = array();
  24. /** @var string */
  25. private $source;
  26. /** @var array */
  27. private $params = array();
  28. /** @var array compile-time filters */
  29. private $filters = array();
  30. /** @var array run-time helpers */
  31. private $helpers = array();
  32. /** @var array */
  33. private $helperLoaders = array();
  34. /** @var Nette\Caching\IStorage */
  35. private $cacheStorage;
  36. /**
  37. * Sets template source code.
  38. * @param string
  39. * @return Template provides a fluent interface
  40. */
  41. public function setSource($source)
  42. {
  43. $this->source = $source;
  44. return $this;
  45. }
  46. /**
  47. * Returns template source code.
  48. * @return source
  49. */
  50. public function getSource()
  51. {
  52. return $this->source;
  53. }
  54. /********************* rendering ****************d*g**/
  55. /**
  56. * Renders template to output.
  57. * @return void
  58. */
  59. public function render()
  60. {
  61. $cache = new Caching\Cache($storage = $this->getCacheStorage(), 'Nette.Template');
  62. $cached = $compiled = $cache->load($this->source);
  63. if ($compiled === NULL) {
  64. $compiled = $this->compile();
  65. $cache->save($this->source, $compiled, array(Caching\Cache::CONSTS => 'Nette\Framework::REVISION'));
  66. $cached = $cache->load($this->source);
  67. }
  68. if ($cached !== NULL && $storage instanceof Caching\Storages\PhpFileStorage) {
  69. Nette\Utils\LimitedScope::load($cached['file'], $this->getParams());
  70. } else {
  71. Nette\Utils\LimitedScope::evaluate($compiled, $this->getParams());
  72. }
  73. }
  74. /**
  75. * Renders template to file.
  76. * @param string
  77. * @return void
  78. */
  79. public function save($file)
  80. {
  81. if (file_put_contents($file, $this->__toString(TRUE)) === FALSE) {
  82. throw new Nette\IOException("Unable to save file '$file'.");
  83. }
  84. }
  85. /**
  86. * Renders template to string.
  87. * @param bool can throw exceptions? (hidden parameter)
  88. * @return string
  89. */
  90. public function __toString()
  91. {
  92. $args = func_get_args();
  93. ob_start();
  94. try {
  95. $this->render();
  96. return ob_get_clean();
  97. } catch (\Exception $e) {
  98. ob_end_clean();
  99. if ($args && $args[0]) {
  100. throw $e;
  101. } else {
  102. Nette\Diagnostics\Debugger::toStringException($e);
  103. }
  104. }
  105. }
  106. /**
  107. * Applies filters on template content.
  108. * @return string
  109. */
  110. public function compile()
  111. {
  112. if (!$this->filters) {
  113. $this->onPrepareFilters($this);
  114. }
  115. $code = $this->getSource();
  116. foreach ($this->filters as $filter) {
  117. $code = self::extractPhp($code, $blocks);
  118. $code = $filter($code);
  119. $code = strtr($code, $blocks); // put PHP code back
  120. }
  121. return self::optimizePhp($code);
  122. }
  123. /********************* template filters & helpers ****************d*g**/
  124. /**
  125. * Registers callback as template compile-time filter.
  126. * @param callback
  127. * @return void
  128. */
  129. public function registerFilter($callback)
  130. {
  131. $callback = callback($callback);
  132. if (in_array($callback, $this->filters)) {
  133. throw new Nette\InvalidStateException("Filter '$callback' was registered twice.");
  134. }
  135. $this->filters[] = $callback;
  136. }
  137. /**
  138. * Returns all registered compile-time filters.
  139. * @return array
  140. */
  141. final public function getFilters()
  142. {
  143. return $this->filters;
  144. }
  145. /**
  146. * Registers callback as template run-time helper.
  147. * @param string
  148. * @param callback
  149. * @return void
  150. */
  151. public function registerHelper($name, $callback)
  152. {
  153. $this->helpers[strtolower($name)] = callback($callback);
  154. }
  155. /**
  156. * Registers callback as template run-time helpers loader.
  157. * @param callback
  158. * @return void
  159. */
  160. public function registerHelperLoader($callback)
  161. {
  162. $this->helperLoaders[] = callback($callback);
  163. }
  164. /**
  165. * Returns all registered run-time helpers.
  166. * @return array
  167. */
  168. final public function getHelpers()
  169. {
  170. return $this->helpers;
  171. }
  172. /**
  173. * Call a template run-time helper. Do not call directly.
  174. * @param string helper name
  175. * @param array arguments
  176. * @return mixed
  177. */
  178. public function __call($name, $args)
  179. {
  180. $lname = strtolower($name);
  181. if (!isset($this->helpers[$lname])) {
  182. foreach ($this->helperLoaders as $loader) {
  183. $helper = $loader($lname);
  184. if ($helper) {
  185. $this->registerHelper($lname, $helper);
  186. return $this->helpers[$lname]->invokeArgs($args);
  187. }
  188. }
  189. return parent::__call($name, $args);
  190. }
  191. return $this->helpers[$lname]->invokeArgs($args);
  192. }
  193. /**
  194. * Sets translate adapter.
  195. * @param Nette\Localization\ITranslator
  196. * @return Template provides a fluent interface
  197. */
  198. public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
  199. {
  200. $this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
  201. return $this;
  202. }
  203. /********************* template parameters ****************d*g**/
  204. /**
  205. * Adds new template parameter.
  206. * @param string name
  207. * @param mixed value
  208. * @return void
  209. */
  210. public function add($name, $value)
  211. {
  212. if (array_key_exists($name, $this->params)) {
  213. throw new Nette\InvalidStateException("The variable '$name' already exists.");
  214. }
  215. $this->params[$name] = $value;
  216. }
  217. /**
  218. * Sets all parameters.
  219. * @param array
  220. * @return Template provides a fluent interface
  221. */
  222. public function setParams(array $params)
  223. {
  224. $this->params = $params + $this->params;
  225. return $this;
  226. }
  227. /**
  228. * Returns array of all parameters.
  229. * @return array
  230. */
  231. public function getParams()
  232. {
  233. $this->params['template'] = $this;
  234. return $this->params;
  235. }
  236. /**
  237. * Sets a template parameter. Do not call directly.
  238. * @param string name
  239. * @param mixed value
  240. * @return void
  241. */
  242. public function __set($name, $value)
  243. {
  244. $this->params[$name] = $value;
  245. }
  246. /**
  247. * Returns a template parameter. Do not call directly.
  248. * @param string name
  249. * @return mixed value
  250. */
  251. public function &__get($name)
  252. {
  253. if ($this->warnOnUndefined && !array_key_exists($name, $this->params)) {
  254. trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
  255. }
  256. return $this->params[$name];
  257. }
  258. /**
  259. * Determines whether parameter is defined. Do not call directly.
  260. * @param string name
  261. * @return bool
  262. */
  263. public function __isset($name)
  264. {
  265. return isset($this->params[$name]);
  266. }
  267. /**
  268. * Removes a template parameter. Do not call directly.
  269. * @param string name
  270. * @return void
  271. */
  272. public function __unset($name)
  273. {
  274. unset($this->params[$name]);
  275. }
  276. /********************* caching ****************d*g**/
  277. /**
  278. * Set cache storage.
  279. * @param Nette\Caching\Cache
  280. * @return void
  281. */
  282. public function setCacheStorage(Caching\IStorage $storage)
  283. {
  284. $this->cacheStorage = $storage;
  285. }
  286. /**
  287. * @return Nette\Caching\IStorage
  288. */
  289. public function getCacheStorage()
  290. {
  291. if ($this->cacheStorage === NULL) {
  292. return new Caching\Storages\DevNullStorage;
  293. }
  294. return $this->cacheStorage;
  295. }
  296. /********************* tools ****************d*g**/
  297. /**
  298. * Extracts all blocks of PHP code.
  299. * @param string
  300. * @param array
  301. * @return string
  302. */
  303. private static function extractPhp($source, & $blocks)
  304. {
  305. $res = '';
  306. $blocks = array();
  307. $tokens = token_get_all($source);
  308. foreach ($tokens as $n => $token) {
  309. if (is_array($token)) {
  310. if ($token[0] === T_INLINE_HTML || $token[0] === T_CLOSE_TAG) {
  311. $res .= $token[1];
  312. continue;
  313. } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
  314. $php = & $res;
  315. $token[1] = '<<?php ?>?';
  316. } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
  317. $res .= $id = "<?php \x01@php:p" . count($blocks) . "@\x02";
  318. $php = & $blocks[$id];
  319. }
  320. $php .= $token[1];
  321. } else {
  322. $php .= $token;
  323. }
  324. }
  325. return $res;
  326. }
  327. /**
  328. * Removes unnecessary blocks of PHP code.
  329. * @param string
  330. * @return string
  331. */
  332. public static function optimizePhp($source, $lineLength = 80, $existenceOfThisParameterSolvesDamnBugInPHP535 = NULL)
  333. {
  334. $res = $php = '';
  335. $lastChar = ';';
  336. $tokens = new \ArrayIterator(token_get_all($source));
  337. foreach ($tokens as $key => $token) {
  338. if (is_array($token)) {
  339. if ($token[0] === T_INLINE_HTML) {
  340. $lastChar = '';
  341. $res .= $token[1];
  342. } elseif ($token[0] === T_CLOSE_TAG) {
  343. $next = isset($tokens[$key + 1]) ? $tokens[$key + 1] : NULL;
  344. if (substr($res, -1) !== '<' && preg_match('#^<\?php\s*$#', $php)) {
  345. $php = ''; // removes empty (?php ?), but retains ((?php ?)?php
  346. } elseif (is_array($next) && $next[0] === T_OPEN_TAG) { // remove ?)(?php
  347. if (!strspn($lastChar, ';{}:/')) {
  348. $php .= $lastChar = ';';
  349. }
  350. if (substr($next[1], -1) === "\n") {
  351. $php .= "\n";
  352. }
  353. $tokens->next();
  354. } elseif ($next) {
  355. $res .= preg_replace('#;?(\s)*$#', '$1', $php) . $token[1]; // remove last semicolon before ?)
  356. if (strlen($res) - strrpos($res, "\n") > $lineLength
  357. && (!is_array($next) || strpos($next[1], "\n") === FALSE)
  358. ) {
  359. $res .= "\n";
  360. }
  361. $php = '';
  362. } else { // remove last ?)
  363. if (!strspn($lastChar, '};')) {
  364. $php .= ';';
  365. }
  366. }
  367. } elseif ($token[0] === T_ELSE || $token[0] === T_ELSEIF) {
  368. if ($tokens[$key + 1] === ':' && $lastChar === '}') {
  369. $php .= ';'; // semicolon needed in if(): ... if() ... else:
  370. }
  371. $lastChar = '';
  372. $php .= $token[1];
  373. } else {
  374. if (!in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG))) {
  375. $lastChar = '';
  376. }
  377. $php .= $token[1];
  378. }
  379. } else {
  380. $php .= $lastChar = $token;
  381. }
  382. }
  383. return $res . $php;
  384. }
  385. }