PageRenderTime 63ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/include/Savant/Savant2.php

https://github.com/radicaldesigns/amp
PHP | 1488 lines | 486 code | 217 blank | 785 comment | 111 complexity | b8c51367655434c9647d9f24f196dc28 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * Error constants.
  5. *
  6. */
  7. define('SAVANT2_ERROR_ASSIGN', -1);
  8. define('SAVANT2_ERROR_ASSIGNREF', -2);
  9. define('SAVANT2_ERROR_COMPILER', -3);
  10. define('SAVANT2_ERROR_NOFILTER', -4);
  11. define('SAVANT2_ERROR_NOPLUGIN', -5);
  12. define('SAVANT2_ERROR_NOSCRIPT', -6);
  13. define('SAVANT2_ERROR_NOTEMPLATE', -7);
  14. define('SAVANT2_ERROR_COMPILE_FAIL', -8);
  15. /**
  16. *
  17. * Error messages.
  18. *
  19. */
  20. if (! isset($GLOBALS['_SAVANT2']['error'])) {
  21. $GLOBALS['_SAVANT2']['error'] = array(
  22. SAVANT2_ERROR_ASSIGN => 'assign() parameters not correct',
  23. SAVANT2_ERROR_ASSIGNREF => 'assignRef() parameters not correct',
  24. SAVANT2_ERROR_COMPILER => 'compiler not an object or has no compile() method',
  25. SAVANT2_ERROR_NOFILTER => 'filter file not found',
  26. SAVANT2_ERROR_NOPLUGIN => 'plugin file not found',
  27. SAVANT2_ERROR_NOSCRIPT => 'compiled template script file not found',
  28. SAVANT2_ERROR_NOTEMPLATE => 'template source file not found',
  29. SAVANT2_ERROR_COMPILE_FAIL => 'template source failed to compile'
  30. );
  31. }
  32. /**
  33. *
  34. * Provides an object-oriented template system.
  35. *
  36. * Savant2 helps you separate model logic from view logic using PHP as
  37. * the template language. By default, Savant2 does not compile templates.
  38. * However, you may pass an optional compiler object to compile template
  39. * source to include-able PHP code.
  40. *
  41. * Please see the documentation at {@link http://phpsavant.com/}, and be
  42. * sure to donate! :-)
  43. *
  44. * $Id: Savant2.php,v 1.22 2005/01/29 15:05:22 pmjones Exp $
  45. *
  46. * @author Paul M. Jones <pmjones@ciaweb.net>
  47. *
  48. * @package Savant2
  49. *
  50. * @version 2.3.3 stable
  51. *
  52. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  53. *
  54. * This program is free software; you can redistribute it and/or modify
  55. * it under the terms of the GNU Lesser General Public License as
  56. * published by the Free Software Foundation; either version 2.1 of the
  57. * License, or (at your option) any later version.
  58. *
  59. * This program is distributed in the hope that it will be useful, but
  60. * WITHOUT ANY WARRANTY; without even the implied warranty of
  61. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  62. * Lesser General Public License for more details.
  63. *
  64. */
  65. class Savant2 {
  66. /**
  67. *
  68. * PHP5 ONLY: What method __call() will alias to.
  69. *
  70. * Generally 'plugin' or 'splugin' (as __call() is intended for those).
  71. *
  72. * @access private
  73. *
  74. * @var string
  75. *
  76. */
  77. var $_call = 'plugin';
  78. /**
  79. *
  80. * The custom compiler (pre-processor) object, if any.
  81. *
  82. * @access private
  83. *
  84. * @var object
  85. *
  86. */
  87. var $_compiler = null;
  88. /**
  89. *
  90. * The class type to use when instantiating error objects.
  91. *
  92. * @access private
  93. *
  94. * @var string
  95. *
  96. */
  97. var $_error = null;
  98. /**
  99. *
  100. * Whether or not to extract assigned variables into fetch() scope.
  101. *
  102. * When true, all variables and references assigned to Savant2 are
  103. * extracted into the local scope of the template script at fetch()
  104. * time, and may be addressed as "$varname" instead of
  105. * "$this->varname". The "$this->varname" notation will also work.
  106. *
  107. * When false, you //must// use "$this->varname" in your templates to
  108. * address a variable instead of "$varname". This has three
  109. * benefits: speed (no time spent extracting variables), memory use
  110. * (saves RAM by not making new references to variables), and clarity
  111. * (any $this->varname is obviously an assigned var, and vars created
  112. * within the template are not prefixed with $this).
  113. *
  114. * @access private
  115. *
  116. * @var bool
  117. *
  118. */
  119. var $_extract = false;
  120. /**
  121. *
  122. * The output of the template script.
  123. *
  124. * @access private
  125. *
  126. * @var string
  127. *
  128. */
  129. var $_output = null;
  130. /**
  131. *
  132. * The set of search directories for resources (plugins/filters) and
  133. * templates.
  134. *
  135. * @access private
  136. *
  137. * @var array
  138. *
  139. */
  140. var $_path = array(
  141. 'resource' => array(),
  142. 'template' => array()
  143. );
  144. /**
  145. *
  146. * Array of resource (plugin/filter) object instances.
  147. *
  148. * @access private
  149. *
  150. * @var array
  151. *
  152. */
  153. var $_resource = array(
  154. 'plugin' => array(),
  155. 'filter' => array()
  156. );
  157. /**
  158. *
  159. * Whether or not to automatically self-reference in plugins and filters.
  160. *
  161. * @access private
  162. *
  163. * @var bool
  164. *
  165. */
  166. var $_reference = false;
  167. /**
  168. *
  169. * The path to the compiled template script file.
  170. *
  171. * By default, the template source and template script are the same file.
  172. *
  173. * @access private
  174. *
  175. * @var string
  176. *
  177. */
  178. var $_script = null;
  179. /**
  180. *
  181. * The name of the default template source file.
  182. *
  183. * @access private
  184. *
  185. * @var string
  186. *
  187. */
  188. var $_template = null;
  189. var $_restrict = false;
  190. // -----------------------------------------------------------------
  191. //
  192. // Constructor and general property setters
  193. //
  194. // -----------------------------------------------------------------
  195. /**
  196. *
  197. * Constructor.
  198. *
  199. * @access public
  200. *
  201. * @param array $conf An associative array of configuration keys for
  202. * the Savant2 object. Any, or none, of the keys may be set. The
  203. * keys are:
  204. *
  205. * 'template_path' => The default path string or array of directories
  206. * to search for templates.
  207. *
  208. * 'resource_path' => The default path string or array of directories
  209. * to search for plugin and filter resources.
  210. *
  211. * 'error' => The custom error class that Savant2 should use
  212. * when returning errors.
  213. *
  214. * 'extract' => Whether or not to extract variables into the local
  215. * scope when executing a template.
  216. *
  217. * 'template' => The default template source name to use.
  218. *
  219. */
  220. function Savant2($conf = array())
  221. {
  222. // set the default template search dirs
  223. if (isset($conf['template_path'])) {
  224. // user-defined dirs
  225. $this->setPath('template', $conf['template_path']);
  226. } else {
  227. // default directory only
  228. $this->setPath('template', null);
  229. }
  230. // set the default filter search dirs
  231. if (isset($conf['resource_path'])) {
  232. // user-defined dirs
  233. $this->setPath('resource', $conf['resource_path']);
  234. } else {
  235. // default directory only
  236. $this->setPath('resource', null);
  237. }
  238. // set the error class
  239. if (isset($conf['error'])) {
  240. $this->setError($conf['error']);
  241. }
  242. // set the extraction flag
  243. if (isset($conf['extract'])) {
  244. $this->setExtract($conf['extract']);
  245. }
  246. // set the restrict flag
  247. if (isset($conf['restrict'])) {
  248. $this->setRestrict($conf['restrict']);
  249. }
  250. // set the Savant reference flag
  251. if (isset($conf['reference'])) {
  252. $this->setReference($conf['reference']);
  253. }
  254. // set the default template
  255. if (isset($conf['template'])) {
  256. $this->setTemplate($conf['template']);
  257. }
  258. }
  259. /**
  260. *
  261. * Sets a custom compiler/pre-processor for template sources.
  262. *
  263. * By default, Savant2 does not use a compiler; use this to set your
  264. * own custom compiler (pre-processor) for template sources.
  265. *
  266. * @access public
  267. *
  268. * @param object $compiler The compiler object; it must have a
  269. * "compile()" method. If null or false, the current compiler object
  270. * is removed from Savant2.
  271. *
  272. * @return void
  273. *
  274. * @throws object An error object with a SAVANT2_ERROR_COMPILER code.
  275. *
  276. */
  277. function setCompiler(&$compiler)
  278. {
  279. if (! $compiler) {
  280. // nullify any current compiler
  281. $this->_compiler = null;
  282. } elseif (is_object($compiler) && method_exists($compiler, 'compile')) {
  283. // refer to a compiler object
  284. $this->_compiler = $compiler;
  285. } else {
  286. // no usable compiler passed
  287. $this->_compiler = null;
  288. return $this->error(SAVANT2_ERROR_COMPILER);
  289. }
  290. }
  291. /**
  292. *
  293. * Sets the method that __call() will alias to.
  294. *
  295. * @access public
  296. *
  297. * @param string $method The Savant2 method for __call() to alias to,
  298. * generally 'plugin' or 'splugin'.
  299. *
  300. * @return void
  301. *
  302. */
  303. function setCall($method = 'plugin')
  304. {
  305. $this->_call = $method;
  306. }
  307. /**
  308. *
  309. * Sets the custom error class for Savant2 errors.
  310. *
  311. * @access public
  312. *
  313. * @param string $error The name of the custom error class name; if
  314. * null or false, resets the error class to 'Savant2_Error'.
  315. *
  316. * @return void
  317. *
  318. */
  319. function setError($error)
  320. {
  321. if (! $error) {
  322. $this->_error = null;
  323. } else {
  324. $this->_error = $error;
  325. }
  326. }
  327. /**
  328. *
  329. * Turns path checking on/off.
  330. *
  331. * @access public
  332. *
  333. * @param bool $flag True to turn on path checks, false to turn off.
  334. *
  335. * @return void
  336. *
  337. */
  338. function setRestrict($flag = false)
  339. {
  340. if ($flag) {
  341. $this->_restrict = true;
  342. } else {
  343. $this->_restrict = false;
  344. }
  345. }
  346. /**
  347. *
  348. * Turns extraction of variables on/off.
  349. *
  350. * @access public
  351. *
  352. * @param bool $flag True to turn on extraction, false to turn off.
  353. *
  354. * @return void
  355. *
  356. */
  357. function setExtract($flag = true)
  358. {
  359. if ($flag) {
  360. $this->_extract = true;
  361. } else {
  362. $this->_extract = false;
  363. }
  364. }
  365. /**
  366. *
  367. * Sets the automated Savant reference for plugins and filters.
  368. *
  369. * @access public
  370. *
  371. * @param bool $flag Whether to reference Savant2 or not.
  372. *
  373. * @return void
  374. *
  375. */
  376. function setReference($flag = false)
  377. {
  378. $this->_reference = $flag;
  379. }
  380. /**
  381. *
  382. * Sets the default template name.
  383. *
  384. * @access public
  385. *
  386. * @param string $template The default template name.
  387. *
  388. * @return void
  389. *
  390. */
  391. function setTemplate($template)
  392. {
  393. $this->_template = $template;
  394. }
  395. // -----------------------------------------------------------------
  396. //
  397. // Path management and file finding
  398. //
  399. // -----------------------------------------------------------------
  400. /**
  401. *
  402. * Sets an entire array of search paths.
  403. *
  404. * @access public
  405. *
  406. * @param string $type The type of path to set, typcially 'template'
  407. * or 'resource'.
  408. *
  409. * @param string|array $new The new set of search paths. If null or
  410. * false, resets to the current directory only.
  411. *
  412. * @return void
  413. *
  414. */
  415. function setPath($type, $new)
  416. {
  417. // clear out the prior search dirs
  418. $this->_path[$type] = array();
  419. // convert from string to path
  420. if (is_string($new) && ! strpos('://', $new)) {
  421. // the search config is a string, and it's not a stream
  422. // identifier (the "://" piece), add it as a path
  423. // string.
  424. $new = explode(PATH_SEPARATOR, $new);
  425. } else {
  426. // force to array
  427. settype($new, 'array');
  428. }
  429. // always add the fallback directories as last resort
  430. switch (strtolower($type)) {
  431. case 'template':
  432. $this->addPath($type, '.');
  433. break;
  434. case 'resource':
  435. $this->addPath($type, dirname(__FILE__) . '/Savant2/');
  436. break;
  437. }
  438. // actually add the user-specified directories
  439. foreach ($new as $dir) {
  440. $this->addPath($type, $dir);
  441. }
  442. }
  443. /**
  444. *
  445. * Adds a search directory for templates.
  446. *
  447. * @access public
  448. *
  449. * @param string $dir The directory or stream to search.
  450. *
  451. * @return void
  452. *
  453. */
  454. function addPath($type, $dir)
  455. {
  456. // no surrounding spaces allowed!
  457. $dir = trim($dir);
  458. // add trailing separators as needed
  459. if (strpos($dir, '://') && substr($dir, -1) != '/') {
  460. // stream
  461. $dir .= '/';
  462. } elseif (substr($dir, -1) != DIRECTORY_SEPARATOR) {
  463. // directory
  464. $dir .= DIRECTORY_SEPARATOR;
  465. }
  466. // add to the top of the search dirs
  467. array_unshift($this->_path[$type], $dir);
  468. }
  469. /**
  470. *
  471. * Gets the array of search directories for template sources.
  472. *
  473. * @access public
  474. *
  475. * @return array The array of search directories for template sources.
  476. *
  477. */
  478. function getPath($type = null)
  479. {
  480. if (! $type) {
  481. return $this->_path;
  482. } else {
  483. return $this->_path[$type];
  484. }
  485. }
  486. /**
  487. *
  488. * Searches a series of paths for a given file.
  489. *
  490. * @param array $type The type of paths to search (template, plugin,
  491. * or filter).
  492. *
  493. * @param string $file The file name to look for.
  494. *
  495. * @return string|bool The full path and file name for the target file,
  496. * or boolean false if the file is not found in any of the paths.
  497. *
  498. */
  499. function findFile($type, $file)
  500. {
  501. // get the set of paths
  502. $set = $this->getPath($type);
  503. // start looping through them
  504. foreach ($set as $path) {
  505. // get the path to the file
  506. $fullname = $path . $file;
  507. // are we doing path checks?
  508. if (! $this->_restrict) {
  509. // no. this is faster but less secure.
  510. if (file_exists($fullname) && is_readable($fullname)) {
  511. return $fullname;
  512. }
  513. } else {
  514. // yes. this is slower, but attempts to restrict
  515. // access only to defined paths.
  516. // is the path based on a stream?
  517. if (strpos('://', $path) === false) {
  518. // not a stream, so do a realpath() to avoid
  519. // directory traversal attempts on the local file
  520. // system. Suggested by Ian Eure, initially
  521. // rejected, but then adopted when the secure
  522. // compiler was added.
  523. $path = realpath($path); // needed for substr() later
  524. $fullname = realpath($fullname);
  525. }
  526. // the substr() check added by Ian Eure to make sure
  527. // that the realpath() results in a directory registered
  528. // with Savant so that non-registered directores are not
  529. // accessible via directory traversal attempts.
  530. if (file_exists($fullname) && is_readable($fullname) &&
  531. substr($fullname, 0, strlen($path)) == $path) {
  532. return $fullname;
  533. }
  534. }
  535. }
  536. // could not find the file in the set of paths
  537. return false;
  538. }
  539. // -----------------------------------------------------------------
  540. //
  541. // Variable and reference assignment
  542. //
  543. // -----------------------------------------------------------------
  544. /**
  545. *
  546. * Sets variables for the template.
  547. *
  548. * This method is overloaded; you can assign all the properties of
  549. * an object, an associative array, or a single value by name.
  550. *
  551. * You are not allowed to set variables that begin with an underscore;
  552. * these are either private properties for Savant2 or private variables
  553. * within the template script itself.
  554. *
  555. * <code>
  556. *
  557. * $Savant2 = new Savant2();
  558. *
  559. * // assign directly
  560. * $Savant2->var1 = 'something';
  561. * $Savant2->var2 = 'else';
  562. *
  563. * // assign by name and value
  564. * $Savant2->assign('var1', 'something');
  565. * $Savant2->assign('var2', 'else');
  566. *
  567. * // assign by assoc-array
  568. * $ary = array('var1' => 'something', 'var2' => 'else');
  569. * $Savant2->assign($obj);
  570. *
  571. * // assign by object
  572. * $obj = new stdClass;
  573. * $obj->var1 = 'something';
  574. * $obj->var2 = 'else';
  575. * $Savant2->assign($obj);
  576. *
  577. * </code>
  578. *
  579. * Greg Beaver came up with the idea of assigning to public class
  580. * properties.
  581. *
  582. * @access public
  583. *
  584. * @return void
  585. *
  586. * @throws object An error object with a SAVANT2_ERROR_ASSIGN code.
  587. *
  588. */
  589. function assign()
  590. {
  591. // this method is overloaded.
  592. $arg = func_get_args();
  593. // must have at least one argument. no error, just do nothing.
  594. if (! isset($arg[0])) {
  595. return;
  596. }
  597. // assign by object
  598. if (is_object($arg[0])) {
  599. // assign public properties
  600. foreach (get_object_vars($arg[0]) as $key => $val) {
  601. if (substr($key, 0, 1) != '_') {
  602. $this->$key = $val;
  603. }
  604. }
  605. return;
  606. }
  607. // assign by associative array
  608. if (is_array($arg[0])) {
  609. foreach ($arg[0] as $key => $val) {
  610. if (substr($key, 0, 1) != '_') {
  611. $this->$key = $val;
  612. }
  613. }
  614. return;
  615. }
  616. // assign by string name and mixed value.
  617. //
  618. // we use array_key_exists() instead of isset() becuase isset()
  619. // fails if the value is set to null.
  620. if (is_string($arg[0]) &&
  621. substr($arg[0], 0, 1) != '_' &&
  622. array_key_exists(1, $arg)) {
  623. $this->$arg[0] = $arg[1];
  624. } else {
  625. return $this->error(SAVANT2_ERROR_ASSIGN, $arg);
  626. }
  627. }
  628. /**
  629. *
  630. * Sets references for the template.
  631. *
  632. * // assign by name and value
  633. * $Savant2->assignRef('ref', $reference);
  634. *
  635. * // assign directly
  636. * $Savant2->ref = $reference;
  637. *
  638. * Greg Beaver came up with the idea of assigning to public class
  639. * properties.
  640. *
  641. * @access public
  642. *
  643. * @param string $key The name for the reference in the template.
  644. *
  645. * @param mixed &$val The referenced variable.
  646. *
  647. * @return void
  648. *
  649. * @throws object An error object with a SAVANT2_ERROR_ASSIGNREF code.
  650. *
  651. */
  652. function assignRef($key, &$val)
  653. {
  654. if (is_string($key) && substr($key, 0, 1) != '_') {
  655. $this->$key = $val;
  656. } else {
  657. return $this->error(
  658. SAVANT2_ERROR_ASSIGNREF,
  659. array('key' => $key, 'val' => $val)
  660. );
  661. }
  662. }
  663. /**
  664. *
  665. * Unsets assigned variables and references.
  666. *
  667. * @access public
  668. *
  669. * @param mixed $var If null, clears all variables; if a string, clears
  670. * the one variable named by the string; if a sequential array, clears
  671. * the variables names in that array.
  672. *
  673. * @return void
  674. *
  675. */
  676. function clear($var = null)
  677. {
  678. if (is_null($var)) {
  679. // clear all variables
  680. $var = array_keys(get_object_vars($this));
  681. } else {
  682. // clear specific variables
  683. settype($var, 'array');
  684. }
  685. // clear out the selected variables
  686. foreach ($var as $name) {
  687. if (substr($name, 0, 1) != '_' && isset($this->$name)) {
  688. unset($this->$name);
  689. }
  690. }
  691. }
  692. /**
  693. *
  694. * Gets the current value of one, many, or all assigned variables.
  695. *
  696. * Never returns variables starting with an underscore; these are
  697. * reserved for internal Savant2 use.
  698. *
  699. * @access public
  700. *
  701. * @param mixed $key If null, returns a copy of all variables and
  702. * their values; if an array, returns an only those variables named
  703. * in the array; if a string, returns only that variable.
  704. *
  705. * @return mixed If multiple variables were reqested, returns an
  706. * associative array where the key is the variable name and the
  707. * value is the variable value; if one variable was requested,
  708. * returns the variable value only.
  709. *
  710. */
  711. function getVars($key = null)
  712. {
  713. if (is_null($key)) {
  714. $key = array_keys(get_object_vars($this));
  715. }
  716. if (is_array($key)) {
  717. // return a series of vars
  718. $tmp = array();
  719. foreach ($key as $var) {
  720. if (substr($var, 0, 1) != '_' && isset($this->$var)) {
  721. $tmp[$var] = $this->$var;
  722. }
  723. }
  724. return $tmp;
  725. } else {
  726. // return a single var
  727. if (substr($key, 0, 1) != '_' && isset($this->$key)) {
  728. return $this->$key;
  729. }
  730. }
  731. }
  732. // -----------------------------------------------------------------
  733. //
  734. // Template processing
  735. //
  736. // -----------------------------------------------------------------
  737. /**
  738. *
  739. * Loads a template script for execution (does not execute the script).
  740. *
  741. * This will optionally compile the template source into a PHP script
  742. * if a compiler object has been passed into Savant2.
  743. *
  744. * Also good for including templates from the template paths within
  745. * another template, like so:
  746. *
  747. * include $this->loadTemplate('template.tpl.php');
  748. *
  749. * @access public
  750. *
  751. * @param string $tpl The template source name to look for.
  752. *
  753. * @param bool $setScript Default false; if true, sets the $this->_script
  754. * property to the resulting script path (or null on error). Normally,
  755. * only $this->fetch() will need to set this to true.
  756. *
  757. * @return string The full path to the compiled template script.
  758. *
  759. * @throws object An error object with a SAVANT2_ERROR_NOTEMPLATE code.
  760. *
  761. */
  762. function loadTemplate($tpl = null, $setScript = false)
  763. {
  764. // set to default template if none specified.
  765. if (is_null($tpl)) {
  766. $tpl = $this->_template;
  767. }
  768. // find the template source.
  769. $file = $this->findFile('template', $tpl);
  770. if (! $file) {
  771. return $this->error(
  772. SAVANT2_ERROR_NOTEMPLATE,
  773. array('template' => $tpl)
  774. );
  775. }
  776. // are we compiling source into a script?
  777. if (is_object($this->_compiler)) {
  778. // compile the template source and get the path to the
  779. // compiled script (will be returned instead of the
  780. // source path)
  781. $result = $this->_compiler->compile($file);
  782. } else {
  783. // no compiling requested, return the source path
  784. $result = $file;
  785. }
  786. // is there a script from the compiler?
  787. if (! $result || $this->isError($result)) {
  788. if ($setScript) {
  789. $this->_script = null;
  790. }
  791. // return an error, along with any error info
  792. // generated by the compiler.
  793. return $this->error(
  794. SAVANT2_ERROR_NOSCRIPT,
  795. array(
  796. 'template' => $tpl,
  797. 'compiler' => $result
  798. )
  799. );
  800. } else {
  801. if ($setScript) {
  802. $this->_script = $result;
  803. }
  804. return $result;
  805. }
  806. }
  807. /**
  808. *
  809. * This is a an alias to loadTemplate() that cannot set the script.
  810. *
  811. * @access public
  812. *
  813. * @param string $tpl The template source name to look for.
  814. *
  815. * @return string The full path to the compiled template script.
  816. *
  817. * @throws object An error object with a SAVANT2_ERROR_NOTEMPLATE code.
  818. *
  819. */
  820. function findTemplate($tpl = null)
  821. {
  822. return $this->loadTemplate($tpl, false);
  823. }
  824. /**
  825. *
  826. * Executes a template script and returns the results as a string.
  827. *
  828. * @param string $_tpl The name of the template source file ...
  829. * automatically searches the template paths and compiles as needed.
  830. *
  831. * @return string The output of the the template script.
  832. *
  833. * @throws object An error object with a SAVANT2_ERROR_NOSCRIPT code.
  834. *
  835. */
  836. function fetch($_tpl = null)
  837. {
  838. // clear prior output
  839. $this->_output = null;
  840. // load the template script
  841. $_result = $this->loadTemplate($_tpl, true);
  842. // is there a template script to be processed?
  843. if ($this->isError($_result)) {
  844. return $_result;
  845. }
  846. // unset so as not to introduce into template scope
  847. unset($_tpl);
  848. unset($_result);
  849. // never allow a 'this' property
  850. if (isset($this->this)) {
  851. unset($this->this);
  852. }
  853. // are we extracting variables into local scope?
  854. if ($this->_extract) {
  855. // extract references to this object's public properties.
  856. // this allows variables assigned by-reference to refer all
  857. // the way back to the model logic. variables assigned
  858. // by-copy only refer back to the property.
  859. foreach (array_keys(get_object_vars($this)) as $_prop) {
  860. if (substr($_prop, 0, 1) != '_') {
  861. // set a variable-variable to an object property
  862. // reference
  863. $$_prop = $this->$_prop;
  864. }
  865. }
  866. // unset private loop vars
  867. unset($_prop);
  868. }
  869. // start capturing output into a buffer
  870. ob_start();
  871. // include the requested template filename in the local scope
  872. // (this will execute the view logic).
  873. include $this->_script;
  874. // done with the requested template; get the buffer and
  875. // clear it.
  876. $this->_output = ob_get_contents();
  877. ob_end_clean();
  878. // done!
  879. return $this->applyFilters();
  880. }
  881. /**
  882. *
  883. * Execute and display a template script.
  884. *
  885. * @param string $tpl The name of the template file to parse;
  886. * automatically searches through the template paths.
  887. *
  888. * @return void
  889. *
  890. * @throws object An error object with a SAVANT2_ERROR_NOSCRIPT code.
  891. *
  892. * @see fetch()
  893. *
  894. */
  895. function display($tpl = null)
  896. {
  897. $result = $this->fetch($tpl);
  898. if ($this->isError($result)) {
  899. return $result;
  900. } else {
  901. echo $result;
  902. }
  903. }
  904. // -----------------------------------------------------------------
  905. //
  906. // Plugins
  907. //
  908. // -----------------------------------------------------------------
  909. /**
  910. *
  911. * Loads a plugin class and instantiates it within Savant2.
  912. *
  913. * @access public
  914. *
  915. * @param string $name The plugin name (not including Savant2_Plugin_
  916. * prefix).
  917. *
  918. * @param array $conf An associative array of plugin configuration
  919. * options.
  920. *
  921. * @param bool $savantRef Default false. When true, sets the $Savant
  922. * property of the filter to a reference to this Savant object.
  923. *
  924. * @return void
  925. *
  926. * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
  927. *
  928. */
  929. function loadPlugin($name, $conf = array(), $savantRef = null)
  930. {
  931. // if no $savantRef is provided, use the default.
  932. if (is_null($savantRef)) {
  933. $savantRef = $this->_reference;
  934. }
  935. // some basic information
  936. $class = "Savant2_Plugin_$name";
  937. $file = "$class.php";
  938. // is it loaded?
  939. if (! class_exists($class)) {
  940. $result = $this->findFile('resource', $file);
  941. if (! $result) {
  942. return $this->error(
  943. SAVANT2_ERROR_NOPLUGIN,
  944. array('plugin' => $name)
  945. );
  946. } else {
  947. include_once $result;
  948. }
  949. }
  950. // is it instantiated?
  951. if (! isset($this->_resource['plugin'][$name]) ||
  952. ! is_object($this->_resource['plugin'][$name]) ||
  953. ! is_a($this->_resource['plugin'][$name], $class)) {
  954. // instantiate it
  955. $this->_resource['plugin'][$name] = new $class($conf);
  956. // add a Savant reference if requested
  957. if ($savantRef) {
  958. $this->_resource['plugin'][$name]->Savant = $this;
  959. }
  960. }
  961. }
  962. /**
  963. *
  964. * Unloads one or more plugins from Savant2.
  965. *
  966. * @access public
  967. *
  968. * @param string|array $name The plugin name (not including Savant2_Plugin_
  969. * prefix). If null, unloads all plugins; if a string, unloads that one
  970. * plugin; if an array, unloads all plugins named as values in the array.
  971. *
  972. * @return void
  973. *
  974. */
  975. function unloadPlugin($name = null)
  976. {
  977. if (is_null($name)) {
  978. $this->_resource['plugin'] = array();
  979. } else {
  980. settype($name, 'array');
  981. foreach ($name as $key) {
  982. if (isset($this->_resource['plugin'][$key])) {
  983. unset($this->_resource['plugin'][$key]);
  984. }
  985. }
  986. }
  987. }
  988. /**
  989. *
  990. * Executes a plugin with arbitrary parameters and returns the
  991. * result.
  992. *
  993. * @access public
  994. *
  995. * @param string $name The plugin name (not including Savant2_Plugin_
  996. * prefix).
  997. *
  998. * @return mixed The plugin results.
  999. *
  1000. * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
  1001. *
  1002. * @see loadPlugin()
  1003. *
  1004. */
  1005. function splugin($name)
  1006. {
  1007. // attempt to load the plugin
  1008. $result = $this->loadPlugin($name);
  1009. if ($this->isError($result)) {
  1010. return $result;
  1011. }
  1012. // call the plugin's "plugin()" method with arguments,
  1013. // dropping the first argument (the plugin name)
  1014. $args = func_get_args();
  1015. array_shift($args);
  1016. return call_user_func_array(
  1017. array(&$this->_resource['plugin'][$name], 'plugin'), $args
  1018. );
  1019. }
  1020. /**
  1021. *
  1022. * Executes a plugin with arbitrary parameters and displays the
  1023. * result.
  1024. *
  1025. * @access public
  1026. *
  1027. * @param string $name The plugin name (not including Savant2_Plugin_
  1028. * prefix).
  1029. *
  1030. * @return void
  1031. *
  1032. * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
  1033. *
  1034. */
  1035. function plugin($name)
  1036. {
  1037. $args = func_get_args();
  1038. $result = call_user_func_array(
  1039. array(&$this, 'splugin'),
  1040. $args
  1041. );
  1042. if ($this->isError($result)) {
  1043. return $result;
  1044. } else {
  1045. echo $result;
  1046. }
  1047. }
  1048. /**
  1049. *
  1050. * PHP5 ONLY: Magic method alias to plugin().
  1051. *
  1052. * E.g., instead of $this->plugin('form', ...) you would use
  1053. * $this->form(...). You can set this to use any other Savant2 method
  1054. * by issuing, for example, setCall('splugin') to use splugin() ... which
  1055. * is really the only other sensible choice.
  1056. *
  1057. * @access public
  1058. *
  1059. * @param string $func The plugin name.
  1060. *
  1061. * @param array $args Arguments passed to the plugin.
  1062. *
  1063. * @return void
  1064. *
  1065. * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
  1066. *
  1067. */
  1068. function __call($func, $args)
  1069. {
  1070. // add the plugin name to the args
  1071. array_unshift($args, $func);
  1072. // call the plugin() method
  1073. return call_user_func_array(
  1074. array(&$this, $this->_call),
  1075. $args
  1076. );
  1077. }
  1078. // -----------------------------------------------------------------
  1079. //
  1080. // Filters
  1081. //
  1082. // -----------------------------------------------------------------
  1083. /**
  1084. *
  1085. * Loads a filter class and instantiates it within Savant2.
  1086. *
  1087. * @access public
  1088. *
  1089. * @param string $name The filter name (not including Savant2_Filter_
  1090. * prefix).
  1091. *
  1092. * @param array $conf An associative array of filter configuration
  1093. * options.
  1094. *
  1095. * @param bool $savantRef Default false. When true, sets the $Savant
  1096. * property of the filter to a reference to this Savant object.
  1097. *
  1098. * @return void
  1099. *
  1100. * @throws object An error object with a SAVANT2_ERROR_NOFILTER code.
  1101. *
  1102. */
  1103. function loadFilter($name, $conf = array(), $savantRef = null)
  1104. {
  1105. // if no $savantRef is provided, use the default.
  1106. if (is_null($savantRef)) {
  1107. $savantRef = $this->_reference;
  1108. }
  1109. // some basic information
  1110. $class = "Savant2_Filter_$name";
  1111. $file = "$class.php";
  1112. // is it loaded?
  1113. if (! class_exists($class)) {
  1114. $result = $this->findFile('resource', $file);
  1115. if (! $result) {
  1116. return $this->error(
  1117. SAVANT2_ERROR_NOFILTER,
  1118. array('filter' => $name)
  1119. );
  1120. } else {
  1121. include_once $result;
  1122. }
  1123. }
  1124. // is it instantiated?
  1125. if (! isset($this->_resource['filter'][$name]) ||
  1126. ! is_object($this->_resource['filter'][$name]) ||
  1127. ! is_a($this->_resource['filter'][$name], $class)) {
  1128. // instantiate it
  1129. $this->_resource['filter'][$name] = new $class($conf);
  1130. // add a Savant reference if requested
  1131. if ($savantRef) {
  1132. $this->_resource['filter'][$name]->Savant = $this;
  1133. }
  1134. }
  1135. }
  1136. /**
  1137. *
  1138. * Unloads one or more filters from Savant2.
  1139. *
  1140. * @access public
  1141. *
  1142. * @param string|array $name The filter name (not including Savant2_Filter_
  1143. * prefix). If null, unloads all filters; if a string, unloads that one
  1144. * filter; if an array, unloads all filters named as values in the array.
  1145. *
  1146. * @return void
  1147. *
  1148. */
  1149. function unloadFilter($name = null)
  1150. {
  1151. if (is_null($name)) {
  1152. $this->_resource['filter'] = array();
  1153. } else {
  1154. settype($name, 'array');
  1155. foreach ($name as $key) {
  1156. if (isset($this->_resource['filter'][$key])) {
  1157. unset($this->_resource['filter'][$key]);
  1158. }
  1159. }
  1160. }
  1161. }
  1162. /**
  1163. *
  1164. * Apply all loaded filters, in order, to text.
  1165. *
  1166. * @access public
  1167. *
  1168. * @param string $text The text to which filters should be applied.
  1169. * If null, sets the text to $this->_output.
  1170. *
  1171. * @return string The text after being passed through all loded
  1172. * filters.
  1173. *
  1174. */
  1175. function applyFilters($text = null)
  1176. {
  1177. // set to output text if no text specified
  1178. if (is_null($text)) {
  1179. $text = $this->_output;
  1180. }
  1181. // get the list of filter names...
  1182. $filter = array_keys($this->_resource['filter']);
  1183. // ... and apply them each in turn.
  1184. foreach ($filter as $name) {
  1185. $this->_resource['filter'][$name]->filter($text);
  1186. }
  1187. // done
  1188. return $text;
  1189. }
  1190. // -----------------------------------------------------------------
  1191. //
  1192. // Error handling
  1193. //
  1194. // -----------------------------------------------------------------
  1195. /**
  1196. *
  1197. * Returns an error object.
  1198. *
  1199. * @access public
  1200. *
  1201. * @param int $code A SAVANT2_ERROR_* constant.
  1202. *
  1203. * @param array $info An array of error-specific information.
  1204. *
  1205. * @return object An error object of the type specified by
  1206. * $this->_error.
  1207. *
  1208. */
  1209. function &error($code, $info = array())
  1210. {
  1211. // the error config array
  1212. $conf = array(
  1213. 'code' => $code,
  1214. 'text' => 'Savant2: ',
  1215. 'info' => (array) $info
  1216. );
  1217. // set an error message from the globals
  1218. if (isset($GLOBALS['_SAVANT2']['error'][$code])) {
  1219. $conf['text'] .= $GLOBALS['_SAVANT2']['error'][$code];
  1220. } else {
  1221. $conf['text'] .= '???';
  1222. }
  1223. // set up the error class name
  1224. if ($this->_error) {
  1225. $class = 'Savant2_Error_' . $this->_error;
  1226. } else {
  1227. $class = 'Savant2_Error';
  1228. }
  1229. // set up the error class file name
  1230. $file = $class . '.php';
  1231. // is it loaded?
  1232. if (! class_exists($class)) {
  1233. // find the error class
  1234. $result = $this->findFile('resource', $file);
  1235. if (! $result) {
  1236. // could not find the custom error class, revert to
  1237. // Savant_Error base class.
  1238. $class = 'Savant2_Error';
  1239. $result = dirname(__FILE__) . '/Savant2/Error.php';
  1240. }
  1241. // include the error class
  1242. include_once $result;
  1243. }
  1244. // instantiate and return the error class
  1245. $err = new $class($conf);
  1246. return $err;
  1247. }
  1248. /**
  1249. *
  1250. * Tests if an object is of the Savant2_Error class.
  1251. *
  1252. * @access public
  1253. *
  1254. * @param object &$obj The object to be tested.
  1255. *
  1256. * @return boolean True if $obj is an error object of the type
  1257. * Savant2_Error, or is a subclass that Savant2_Error. False if not.
  1258. *
  1259. */
  1260. function isError(&$obj)
  1261. {
  1262. if (is_object($obj)) {
  1263. if (is_a($obj, 'Savant2_Error') ||
  1264. is_subclass_of($obj, 'Savant2_Error')) {
  1265. return true;
  1266. }
  1267. }
  1268. return false;
  1269. }
  1270. }
  1271. ?>