PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/symphony/lib/toolkit/class.xsltprocess.php

http://github.com/symphonycms/symphony-2
PHP | 388 lines | 323 code | 10 blank | 55 comment | 1 complexity | 3e75aac662c7add895d1c69062820f1e MD5 | raw file
  1. <?php
  2. /**
  3. * @package toolkit
  4. */
  5. /**
  6. * The `XSLTProcess` class is responsible for taking a chunk of XML
  7. * and applying an XSLT stylesheet to it. Custom error handlers are
  8. * used to capture any errors that occurred during this process, and
  9. * are exposed to the `ExceptionHandler`'s for display to the user.
  10. */
  11. class XSLTProcess
  12. {
  13. /**
  14. * An array of all the parameters to be made available during the XSLT
  15. * transform
  16. * @var array
  17. */
  18. protected $_param = array();
  19. /**
  20. * An array of the PHP functions to be made available during the XSLT
  21. * transform
  22. * @var array
  23. */
  24. protected $_registered_php_functions = array();
  25. /**
  26. * Any errors that occur during the transformation are stored in this array.
  27. * @var array
  28. */
  29. private $_errors = array();
  30. /**
  31. * The last context, i.e. xml data that the system uses right now.
  32. * Used when trapping errors, to be able to generate debug info.
  33. * @var string
  34. */
  35. private $_lastContext = null;
  36. /**
  37. * A path where the XSLTProc will write its profiling information.
  38. *
  39. * @var string
  40. */
  41. private $profiling = null;
  42. /**
  43. * Sets the parameters that will output with the resulting page
  44. * and be accessible in the XSLT. This function translates all ' into
  45. * `&apos;`, with the tradeoff being that a <xsl:value-of select='$param' />
  46. * that has a ' will output `&apos;` but the benefit that ' and " can be
  47. * in the params
  48. *
  49. * @link http://www.php.net/manual/en/xsltprocessor.setparameter.php#81077
  50. * @param array $param
  51. * An associative array of params for this page
  52. */
  53. public function setRuntimeParam(array $param)
  54. {
  55. $this->_param = str_replace("'", "&apos;", $param);
  56. }
  57. /**
  58. * Allows the registration of PHP functions to be used on the Frontend
  59. * by passing the function name or an array of function names
  60. *
  61. * @param mixed $function
  62. * Either an array of function names, or just the function name as a
  63. * string
  64. */
  65. public function registerPHPFunction($function)
  66. {
  67. if (is_array($function)) {
  68. $this->_registered_php_functions = array_unique(
  69. array_merge($this->_registered_php_functions, $function)
  70. );
  71. } else {
  72. $this->_registered_php_functions[] = $function;
  73. }
  74. }
  75. /**
  76. * Checks if there is an available `XSLTProcessor`
  77. *
  78. * @return boolean
  79. * true if there is an existing `XSLTProcessor` class, false otherwise
  80. */
  81. public static function isXSLTProcessorAvailable()
  82. {
  83. return (class_exists('XSLTProcessor') || function_exists('xslt_process'));
  84. }
  85. /**
  86. * This function will take a given XML file, a stylesheet and apply
  87. * the transformation. Any errors will call the error function to log
  88. * them into the `$_errors` array
  89. *
  90. * @see toolkit.XSLTProcess#__error()
  91. * @see toolkit.XSLTProcess#__process()
  92. * @param string $xml
  93. * The XML for the transformation to be applied to
  94. * @param string $xsl
  95. * The XSL for the transformation
  96. * @return string|boolean
  97. * The string of the resulting transform, or false if there was an error
  98. */
  99. public function process($xml, $xsl)
  100. {
  101. // dont let process continue if no xsl functionality exists
  102. if (!XSLTProcess::isXSLTProcessorAvailable()) {
  103. return false;
  104. }
  105. $XSLProc = new XSLTProcessor;
  106. if (!empty($this->_registered_php_functions)) {
  107. $XSLProc->registerPHPFunctions($this->_registered_php_functions);
  108. }
  109. if (!empty($this->profiling)) {
  110. $XSLProc->setProfiling($this->profiling);
  111. }
  112. $result = $this->__process(
  113. $XSLProc,
  114. $xml,
  115. $xsl,
  116. $this->_param
  117. );
  118. unset($XSLProc);
  119. return $result;
  120. }
  121. /**
  122. * Uses `DOMDocument` to transform the document. Any errors that
  123. * occur are trapped by custom error handlers, `trapXMLError` or
  124. * `trapXSLError`.
  125. *
  126. * @param XSLTProcessor $XSLProc
  127. * An instance of `XSLTProcessor`
  128. * @param string $xml
  129. * The XML for the transformation to be applied to
  130. * @param string $xsl
  131. * The XSL for the transformation
  132. * @param array $parameters
  133. * An array of available parameters the XSL will have access to
  134. * @return string
  135. */
  136. private function __process(XSLTProcessor $XSLProc, $xml, $xsl, array $parameters = array())
  137. {
  138. // Create instances of the DOMDocument class
  139. $xmlDoc = new DOMDocument;
  140. $xslDoc = new DOMDocument;
  141. // Set up error handling
  142. if (function_exists('ini_set')) {
  143. $ehOLD = ini_set('html_errors', false);
  144. }
  145. // Load the xml document
  146. $this->_lastContext = $xml;
  147. set_error_handler(array($this, 'trapXMLError'));
  148. // Prevent remote entities from being loaded, RE: #1939
  149. $elOLD = libxml_disable_entity_loader(true);
  150. // Remove null bytes from XML
  151. $xml = str_replace(chr(0), '', $xml);
  152. $xmlDoc->loadXML($xml, LIBXML_NONET | LIBXML_DTDLOAD | LIBXML_DTDATTR | defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0);
  153. libxml_disable_entity_loader($elOLD);
  154. // Must restore the error handler to avoid problems
  155. restore_error_handler();
  156. // Load the xsl document
  157. $this->_lastContext = $xsl;
  158. set_error_handler(array($this, 'trapXSLError'));
  159. // Ensure that the XSLT can be loaded with `false`. RE: #1939
  160. // Note that `true` will cause `<xsl:import />` to fail.
  161. $elOLD = libxml_disable_entity_loader(false);
  162. $xslDoc->loadXML($xsl, LIBXML_NONET | LIBXML_DTDLOAD | LIBXML_DTDATTR | defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0);
  163. libxml_disable_entity_loader($elOLD);
  164. // Load the xsl template
  165. $XSLProc->importStyleSheet($xslDoc);
  166. // Set parameters when defined
  167. if (!empty($parameters)) {
  168. General::flattenArray($parameters);
  169. $XSLProc->setParameter('', $parameters);
  170. }
  171. // Must restore the error handler to avoid problems
  172. restore_error_handler();
  173. // Start the transformation
  174. set_error_handler(array($this, 'trapXMLError'));
  175. $processed = $XSLProc->transformToXML($xmlDoc);
  176. // Restore error handling
  177. if (function_exists('ini_set') && isset($ehOLD)) {
  178. ini_set('html_errors', $ehOLD);
  179. }
  180. // Must restore the error handler to avoid problems
  181. restore_error_handler();
  182. $this->_lastContext = null;
  183. return $processed;
  184. }
  185. /**
  186. * That validate function takes an XSD to valid against `$xml`
  187. * returning boolean.
  188. *
  189. * @since Symphony 2.3
  190. * @param string $xsd
  191. * The XSD to validate against
  192. * @param string $xml
  193. * The XML to validate
  194. * @return boolean
  195. * Returns true if the `$xml` validates against `$xsd`, false otherwise.
  196. * If false is returned, the errors can be obtained with `XSLTProcess->getErrors()`
  197. */
  198. public function validate($xsd, $xml)
  199. {
  200. if (is_null($xsd) || is_null($xml)) {
  201. return false;
  202. }
  203. // Create instances of the DOMDocument class
  204. $xmlDoc = new DOMDocument;
  205. // Set up error handling
  206. if (function_exists('ini_set')) {
  207. $ehOLD = ini_set('html_errors', false);
  208. }
  209. // Load the xml document
  210. $this->_lastContext = $xml;
  211. set_error_handler(array($this, 'trapXMLError'));
  212. $elOLD = libxml_disable_entity_loader(true);
  213. $xmlDoc->loadXML($xml, LIBXML_NONET | LIBXML_DTDLOAD | LIBXML_DTDATTR | defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0);
  214. libxml_disable_entity_loader($elOLD);
  215. // Must restore the error handler to avoid problems
  216. restore_error_handler();
  217. // Validate the XML against the XSD
  218. $this->_lastContext = $xsd;
  219. set_error_handler(array($this, 'trapXSDError'));
  220. $result = $xmlDoc->schemaValidateSource($xsd);
  221. // Restore error handling
  222. if (function_exists('ini_set') && isset($ehOLD)) {
  223. ini_set('html_errors', $ehOLD);
  224. }
  225. // Must restore the error handler to avoid problems
  226. restore_error_handler();
  227. $this->_lastContext = null;
  228. return $result;
  229. }
  230. /**
  231. * A custom error handler especially for XML errors.
  232. *
  233. * @link http://au.php.net/manual/en/function.set-error-handler.php
  234. * @param integer $errno
  235. * @param integer $errstr
  236. * @param integer $errfile
  237. * @param integer $errline
  238. */
  239. public function trapXMLError($errno, $errstr, $errfile, $errline)
  240. {
  241. $this->__error($errno, str_replace('DOMDocument::', null, $errstr), $errfile, $errline, 'xml');
  242. }
  243. /**
  244. * A custom error handler especially for XSL errors.
  245. *
  246. * @link http://au.php.net/manual/en/function.set-error-handler.php
  247. * @param integer $errno
  248. * @param integer $errstr
  249. * @param integer $errfile
  250. * @param integer $errline
  251. */
  252. public function trapXSLError($errno, $errstr, $errfile, $errline)
  253. {
  254. $this->__error($errno, str_replace('DOMDocument::', null, $errstr), $errfile, $errline, 'xsl');
  255. }
  256. /**
  257. * A custom error handler especially for XSD errors.
  258. *
  259. * @since Symphony 2.3
  260. * @link http://au.php.net/manual/en/function.set-error-handler.php
  261. * @param integer $errno
  262. * @param integer $errstr
  263. * @param integer $errfile
  264. * @param integer $errline
  265. */
  266. public function trapXSDError($errno, $errstr, $errfile, $errline)
  267. {
  268. $this->__error($errno, str_replace('DOMDocument::', null, $errstr), $errfile, $errline, 'xsd');
  269. }
  270. /**
  271. * Writes an error to the `$_errors` array, which contains the error information
  272. * and some basic debugging information.
  273. *
  274. * @link http://au.php.net/manual/en/function.set-error-handler.php
  275. * @param integer $number
  276. * @param string $message
  277. * @param string $file
  278. * @param string $line
  279. * @param string $type
  280. * Where the error occurred, can be either 'xml', 'xsl' or `xsd`
  281. */
  282. public function __error($number, $message, $file = null, $line = null, $type = null)
  283. {
  284. $this->_errors[] = array(
  285. 'number' => $number,
  286. 'message' => $message,
  287. 'file' => $file,
  288. 'line' => $line,
  289. 'type' => $type,
  290. 'context' => $this->_lastContext,
  291. );
  292. }
  293. /**
  294. * Returns boolean if any errors occurred during the transformation.
  295. *
  296. * @see getError
  297. * @return boolean
  298. */
  299. public function isErrors()
  300. {
  301. return (!empty($this->_errors) ? true : false);
  302. }
  303. /**
  304. * Provides an Iterator interface to return an error from the `$_errors`
  305. * array. Repeat calls to this function to get all errors
  306. *
  307. * @param boolean $all
  308. * If true, return all errors instead of one by one. Defaults to false
  309. * @param boolean $rewind
  310. * If rewind is true, resets the internal array pointer to the start of
  311. * the `$_errors` array. Defaults to false.
  312. * @return array
  313. * Either an array of error array's or just an error array
  314. */
  315. public function getError($all = false, $rewind = false)
  316. {
  317. if ($rewind) {
  318. reset($this->_errors);
  319. }
  320. return ($all ? $this->_errors : each($this->_errors));
  321. }
  322. /**
  323. * Gets the current profiling file path
  324. *
  325. * @return string
  326. */
  327. public function getProfiling()
  328. {
  329. return $this->profiling;
  330. }
  331. /**
  332. * Sets the current profiling file path
  333. *
  334. * @param $profiling string
  335. * The file path
  336. */
  337. public function setProfiling($profiling)
  338. {
  339. $this->profiling = $profiling;
  340. }
  341. }