PageRenderTime 44ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/sparkplug/sparkplug.php

https://code.google.com/p/sparkplug-framework/
PHP | 2320 lines | 1142 code | 300 blank | 878 comment | 103 complexity | a887e62d6aa40a4c8bdcd1d56e6260d1 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. Copyright 2009-2012 Sam Weiss
  4. All Rights Reserved.
  5. This file is part of Spark/Plug.
  6. Spark/Plug is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. if (!defined('spark/plug'))
  18. {
  19. header('HTTP/1.1 403 Forbidden');
  20. exit('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You don\'t have permission to access the requested resource on this server.</p></body></html>');
  21. }
  22. define('spark_plug_version', '1.1.0');
  23. /**
  24. * The SparkException class is our custom exception class.
  25. *
  26. * @package Spark/Plug
  27. */
  28. // -----------------------------------------------------------------------------
  29. class SparkException extends Exception
  30. {
  31. private $_vars;
  32. /**
  33. * Construct a new exception.
  34. *
  35. * @param string $message
  36. * @param int $code
  37. * @return SparkException object
  38. */
  39. public function __construct($message, $code = 0, $vars = NULL)
  40. {
  41. parent::__construct($message, $code);
  42. $this->_vars = empty($vars) ? array() : $vars;
  43. }
  44. /**
  45. * Return vars associated with this exception.
  46. *
  47. * @return array
  48. */
  49. public function vars()
  50. {
  51. return $this->_vars;
  52. }
  53. /**
  54. * Return named var associated with this exception.
  55. *
  56. * @param string $name
  57. * @return mixed
  58. */
  59. public function getVar($name, $default = NULL)
  60. {
  61. return isset($this->_vars[$name]) ? $this->_vars[$name] : $default;
  62. }
  63. }
  64. /**
  65. * The SparkHTTPException class is used to automatically convert HTTP status codes to exceptions.
  66. *
  67. * @package Spark/Plug
  68. */
  69. // -----------------------------------------------------------------------------
  70. class SparkHTTPException extends SparkException
  71. {
  72. private $_httpStatusCode;
  73. private $_httpStatusText;
  74. /**
  75. * Convert HTTP status code to equivalent exception.
  76. *
  77. * @param string $message
  78. * @param int $code
  79. * @return SparkException object
  80. */
  81. public function __construct($httpStatusCode, $httpStatusText, $message = '', $vars = '')
  82. {
  83. parent::__construct($message, 0, $vars);
  84. $this->_httpStatusCode = $httpStatusCode;
  85. $this->_httpStatusText = $httpStatusText;
  86. }
  87. /**
  88. * Return HTTP status code associated with this exception.
  89. *
  90. * @return int
  91. */
  92. public function getHTTPStatusCode()
  93. {
  94. return $this->_httpStatusCode;
  95. }
  96. /**
  97. * Return HTTP status text associated with this exception.
  98. *
  99. * @return string
  100. */
  101. public function getHTTPStatusText()
  102. {
  103. return $this->_httpStatusText;
  104. }
  105. /**
  106. * Return HTTP status messages associated with this exception.
  107. * Suitable for use in HTTP header.
  108. *
  109. * @return string
  110. */
  111. public function getHTTPStatus()
  112. {
  113. return $this->_httpStatusCode . ' ' . $this->_httpStatusText;
  114. }
  115. }
  116. class SparkHTTPException_BadRequest extends SparkHTTPException
  117. {
  118. public function __construct($message = NULL, $vars = NULL)
  119. {
  120. parent::__construct(400, 'Bad Request', $message, $vars);
  121. }
  122. }
  123. class SparkHTTPException_Unauthorized extends SparkHTTPException
  124. {
  125. public function __construct($message = NULL, $vars = NULL)
  126. {
  127. parent::__construct(401, 'Unauthorized', $message, $vars);
  128. }
  129. }
  130. class SparkHTTPException_Forbidden extends SparkHTTPException
  131. {
  132. public function __construct($message = NULL, $vars = NULL)
  133. {
  134. isset($message) || $message = 'Permission denied.';
  135. parent::__construct(403, 'Forbidden', $message, $vars);
  136. }
  137. }
  138. class SparkHTTPException_NotFound extends SparkHTTPException
  139. {
  140. public function __construct($message = NULL, $vars = NULL)
  141. {
  142. isset($message) || $message = 'The page you requested was not found.';
  143. parent::__construct(404, 'Not Found', $message, $vars);
  144. }
  145. }
  146. class SparkHTTPException_MethodNotAllowed extends SparkHTTPException
  147. {
  148. public function __construct($message = NULL, $vars = NULL)
  149. {
  150. parent::__construct(405, 'Method Not Allowed', $message, $vars);
  151. }
  152. }
  153. class SparkHTTPException_Conflict extends SparkHTTPException
  154. {
  155. public function __construct($message = NULL, $vars = NULL)
  156. {
  157. parent::__construct(409, 'Conflict', $message, $vars);
  158. }
  159. }
  160. class SparkHTTPException_Gone extends SparkHTTPException
  161. {
  162. public function __construct($message = NULL, $vars = NULL)
  163. {
  164. parent::__construct(410, 'Gone', $message, $vars);
  165. }
  166. }
  167. class SparkHTTPException_LengthRequired extends SparkHTTPException
  168. {
  169. public function __construct($message = NULL, $vars = NULL)
  170. {
  171. parent::__construct(411, 'Length Required', $message, $vars);
  172. }
  173. }
  174. class SparkHTTPException_RequestEntityTooLarge extends SparkHTTPException
  175. {
  176. public function __construct($message = NULL, $vars = NULL)
  177. {
  178. parent::__construct(413, 'Request Entity Too Large', $message, $vars);
  179. }
  180. }
  181. class SparkHTTPException_RequestURITooLong extends SparkHTTPException
  182. {
  183. public function __construct($message = NULL, $vars = NULL)
  184. {
  185. parent::__construct(414, 'Request-URI Too Long', $message, $vars);
  186. }
  187. }
  188. class SparkHTTPException_UnsupportedMediaType extends SparkHTTPException
  189. {
  190. public function __construct($message = NULL, $vars = NULL)
  191. {
  192. parent::__construct(415, 'Unsupported Media Type', $message, $vars);
  193. }
  194. }
  195. class SparkHTTPException_InternalServerError extends SparkHTTPException
  196. {
  197. public function __construct($message = NULL, $vars = NULL)
  198. {
  199. isset($message) || $message = 'Your request could not be processed.';
  200. parent::__construct(500, 'Internal Server Error', $message, $vars);
  201. }
  202. }
  203. class SparkHTTPException_NotImplemented extends SparkHTTPException
  204. {
  205. public function __construct($message = NULL, $vars = NULL)
  206. {
  207. parent::__construct(501, 'Not Implemented', $message, $vars);
  208. }
  209. }
  210. class SparkHTTPException_ServiceUnavailable extends SparkHTTPException
  211. {
  212. public function __construct($message = NULL, $vars = NULL)
  213. {
  214. parent::__construct(503, 'Service Unavailable', $message, $vars);
  215. }
  216. }
  217. /**
  218. * The SparkPHPException class is used to automatically convert PHP errors to exceptions.
  219. * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
  220. *
  221. * @package Spark/Plug
  222. */
  223. // -----------------------------------------------------------------------------
  224. final class SparkPHPException
  225. {
  226. /**
  227. * Convert PHP error to equivalent exception.
  228. *
  229. * @param int $code
  230. * @param string $message
  231. * @param string $file
  232. * @param int $line
  233. * @throw ErrorException
  234. */
  235. public static function errorHandler($code, $message, $file, $line)
  236. {
  237. throw new ErrorException($message, 0, $code, $file, $line);
  238. }
  239. }
  240. /**
  241. * The SparkUtil class contains some utility functions commonly needed by web apps.
  242. * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
  243. *
  244. * @package Spark/Plug
  245. */
  246. // -----------------------------------------------------------------------------
  247. final class SparkUtil
  248. {
  249. const kRequestMethod_HEAD = 1;
  250. const kRequestMethod_GET = 2;
  251. const kRequestMethod_POST = 3;
  252. const kRequestMethod_PUT = 4;
  253. const kRequestMethod_DELETE = 5;
  254. const kRequestMethod_OPTIONS = 6;
  255. private static $_http_methods = array
  256. (
  257. 'head' => self::kRequestMethod_HEAD,
  258. 'get' => self::kRequestMethod_GET,
  259. 'post' => self::kRequestMethod_POST,
  260. 'put' => self::kRequestMethod_PUT,
  261. 'delete' => self::kRequestMethod_DELETE,
  262. 'options' => self::kRequestMethod_OPTIONS,
  263. );
  264. /**
  265. * Return document root.
  266. *
  267. * @return string
  268. */
  269. final public static function doc_root()
  270. {
  271. return $_SERVER['DOCUMENT_ROOT'];
  272. }
  273. /**
  274. * Return whether this page was accessed securely.
  275. *
  276. * @return boolean
  277. */
  278. final public static function is_https()
  279. {
  280. return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
  281. }
  282. /**
  283. * Return scheme string.
  284. *
  285. * @return string
  286. */
  287. final public static function scheme()
  288. {
  289. return self::is_https() ? 'https://' : 'http://';
  290. }
  291. /**
  292. * Return host string.
  293. *
  294. * @return string
  295. */
  296. final public static function host()
  297. {
  298. return $_SERVER['HTTP_HOST'];
  299. }
  300. /**
  301. * Return server name string.
  302. *
  303. * @return string
  304. */
  305. final public static function server_name()
  306. {
  307. return $_SERVER['SERVER_NAME'];
  308. }
  309. /**
  310. * Return server ip address string.
  311. *
  312. * @return string
  313. */
  314. final public static function server_addr()
  315. {
  316. return $_SERVER['SERVER_ADDR'];
  317. }
  318. /**
  319. * Return script name.
  320. *
  321. * @return string
  322. */
  323. final public static function script_name()
  324. {
  325. return $_SERVER['SCRIPT_NAME'];
  326. }
  327. /**
  328. * Return original request URI.
  329. *
  330. * @return string
  331. */
  332. final public static function request_uri()
  333. {
  334. return $_SERVER['REQUEST_URI'];
  335. }
  336. /**
  337. * Return original request URI minus any query parameters.
  338. *
  339. * @return string
  340. */
  341. final public static function request_uri_base()
  342. {
  343. return self::remove_query_from_url(self::request_uri());
  344. }
  345. /**
  346. * Return original request URL.
  347. *
  348. * @return string
  349. */
  350. final public static function self_url()
  351. {
  352. if (($host = self::host()) === '')
  353. {
  354. $host = self::server_name();
  355. }
  356. return self::scheme() . $host . self::request_uri();
  357. }
  358. /**
  359. * Return original request method.
  360. *
  361. * @return string
  362. */
  363. final public static function request_method()
  364. {
  365. return !empty($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : '';
  366. }
  367. /**
  368. * Return original query string.
  369. *
  370. * @return string
  371. */
  372. final public static function query_string()
  373. {
  374. return !empty($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
  375. }
  376. /**
  377. * Return user's browser agent string.
  378. *
  379. * @return string
  380. */
  381. final public static function user_agent()
  382. {
  383. return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
  384. }
  385. /**
  386. * Return user's IP address.
  387. *
  388. * @return string
  389. */
  390. final public static function remote_ip()
  391. {
  392. return $_SERVER['REMOTE_ADDR'];
  393. }
  394. /**
  395. * Return referring url.
  396. *
  397. * @return string
  398. */
  399. final public static function referrer_url()
  400. {
  401. return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
  402. }
  403. /**
  404. * Return referring url.
  405. *
  406. * @return string
  407. */
  408. final public static function get_http_method($method = NULL)
  409. {
  410. if (!$method)
  411. {
  412. $method = self::request_method();
  413. }
  414. if ($method = @self::$_http_methods[strtolower($method)])
  415. {
  416. return $method;
  417. }
  418. return NULL;
  419. }
  420. /**
  421. * Return boolean indicating whether the current request was made via ajax.
  422. *
  423. * @return boolean
  424. */
  425. final public static function is_ajax_request()
  426. {
  427. return (strtolower(@$_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
  428. }
  429. /**
  430. * Return array of request headers.
  431. *
  432. * @return array
  433. */
  434. final public static function request_headers()
  435. {
  436. if (!function_exists('apache_request_headers'))
  437. {
  438. function apache_request_headers()
  439. {
  440. $headers = array();
  441. foreach($_SERVER as $key => $value)
  442. {
  443. if (substr_compare($key, 'HTTP_', 0, 5) === 0)
  444. {
  445. $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key,5)))));
  446. $headers[$key] = $value;
  447. }
  448. }
  449. return $headers;
  450. }
  451. }
  452. return apache_request_headers();
  453. }
  454. /**
  455. * Check if two IP addresses match or are similar.
  456. *
  457. * @param string $ip1
  458. * @param string $ip2
  459. * @return boolean
  460. */
  461. final public static function match_ip($ip1, $ip2, $matchOctets = 4)
  462. {
  463. // full IP match?
  464. if ($ip1 == $ip2)
  465. {
  466. return true;
  467. }
  468. // entire IP address did not match (probably a pesky proxy server)
  469. // break the address into octets
  470. $octets1 = explode('.' , $ip1, 4);
  471. $octets2 = explode('.' , $ip2, 4);
  472. for ($match = 0; $match < $matchOctets; ++$match)
  473. {
  474. if ($octets1[$match] != $octets2[$match])
  475. {
  476. return false;
  477. }
  478. }
  479. return true;
  480. }
  481. /**
  482. * Check for integer value, and force to type integer if true.
  483. *
  484. * @param any &$val
  485. * @return boolean
  486. */
  487. final public static function valid_int(&$val)
  488. {
  489. if (is_int($val))
  490. {
  491. return true;
  492. }
  493. if (preg_match('/^[-+]?\d+$/', $val))
  494. {
  495. $val = (int)$val;
  496. return true;
  497. }
  498. return false;
  499. }
  500. /**
  501. * Check if URL is valid.
  502. *
  503. * httpurl = https?://{hostport}(/{hpath}(\?{search})?)?
  504. * hostport = host(:{port})?
  505. * host = {hostname}|{hostnumber}
  506. * hostname = ({domainlabel}\.)*{toplabel}
  507. * domainlabel = {alphadigit}(({alphadigit}|\-)*{alphadigit})?
  508. * toplabel = {alpha}(({alphadigit}|\-)*{alphadigit})?
  509. * hostnumber = {digits}\.{digits}\.{digits}\.{digits}
  510. * port = {digits}
  511. * hpath = {hsegment}(/{hsegment})*
  512. * hsegment = ({uchar}|[;:@&=])*
  513. * search = ({uchar}|[;:@&=])*
  514. * uchar = {unreserved}|{escape}
  515. * unreserved = {alphadigit}|{safe}|{extra}
  516. * escape = %{hex}{hex}
  517. * safe = [\$\-\_\.\+]
  518. * extra = [\!\*\'\(\)\,]
  519. * hex = [0-9a-f]
  520. * alpha = [a-z]
  521. * digit = [0-9]
  522. * alphadigit = [a-z0-9]
  523. *
  524. * hsegment = (?:[a-z0-9\$\-\_\.\+\!\*\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*
  525. *
  526. * return preg_match('#^https?://(?:(?:(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)*[a-z](?:[a-z0-9\-]*[a-z0-9])?)|(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3}))(?::[0-9]{1,5})?(?:/(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*(?:/(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)*(?:\?(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)?)?$#i', $item);
  527. *
  528. *
  529. * @param string $url
  530. * @return boolean
  531. */
  532. final public static function valid_url($url, $requireScheme = true)
  533. {
  534. return preg_match('#^(?:https?://)' . ($requireScheme ? '' : '?') . '(?:(?:(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)*[a-z](?:[a-z0-9\-]*[a-z0-9])?)|(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3}))(?::[0-9]{1,5})?(?:/(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)*(?:\?(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)?$#i', $url) ? true : false;
  535. }
  536. /**
  537. * Check if URL path is valid.
  538. *
  539. * @param string $url
  540. * @return boolean
  541. */
  542. final public static function valid_url_path($url)
  543. {
  544. return preg_match('#^(?:/(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)*(?:\?(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)?$#i', $url) ? true : false;
  545. }
  546. /**
  547. * Check if email address is valid according to HTML5 spec (http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#valid-e-mail-address).
  548. *
  549. * @param string $email
  550. * @return boolean
  551. */
  552. final public static function valid_email($email)
  553. {
  554. return preg_match('/^([[:alnum:]!#$%&\'*+-\/=?^_`{|}.])+@[[:alnum:]-]+(\.[[:alnum:]-]+)*$/', $email) ? true : false;
  555. }
  556. /**
  557. * Extract scheme and host from url.
  558. *
  559. * @param string $url
  560. * @return string
  561. */
  562. final public static function extract_scheme_host_from_url($url)
  563. {
  564. return preg_replace('#^(https?://[^/]+)(.*)$#', '$1', $url);
  565. }
  566. /**
  567. * Extract host from url.
  568. *
  569. * @param string $url
  570. * @return string
  571. */
  572. final public static function extract_host_from_url($url)
  573. {
  574. return preg_replace('#^https?://([^/]+)(.*)$#', '$1', $url);
  575. }
  576. /**
  577. * Remove query string from url.
  578. *
  579. * @param string $url
  580. * @return string
  581. */
  582. final public static function remove_query_from_url($url)
  583. {
  584. return preg_replace('/\?.*$/', '', $url);
  585. }
  586. /**
  587. * Create a nice big random string.
  588. *
  589. * @return string
  590. */
  591. final public static function make_nonce($length = 32)
  592. {
  593. $nonce = '';
  594. do
  595. {
  596. $nonce .= sha1(uniqid(mt_rand(), true));
  597. } while (strlen($nonce) < $length);
  598. return substr($nonce, 0, $length);
  599. }
  600. /**
  601. * Create a version 4 random UUID.
  602. *
  603. * @return string
  604. */
  605. final public static function make_uuid($canonical = true)
  606. {
  607. $format = $canonical ? '%04x%04x-%04x-%04x-%04x-%04x%04x%04x' : '%04x%04x%04x%04x%04x%04x%04x%04x';
  608. return sprintf
  609. (
  610. $format,
  611. mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
  612. mt_rand(0, 0x0fff) | 0x4000,
  613. mt_rand(0, 0x3fff) | 0x8000,
  614. mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
  615. );
  616. }
  617. /**
  618. * Encode a UUID into smaller ASCII string.
  619. *
  620. * @param string $uuid
  621. * @return string
  622. */
  623. final public static function encode_uuid($uuid)
  624. {
  625. return str_replace(array('/','+'), array('_','-'), substr(base64_encode(pack('H*', str_replace('-', '', $uuid))), 0, 22));
  626. }
  627. /**
  628. * Decode an encoded UUID.
  629. *
  630. * @param string $uuid
  631. * @return string
  632. */
  633. final public static function decode_uuid($uuid)
  634. {
  635. $decoded = bin2hex(base64_decode(str_replace(array('_','-'), array('/','+'), $uuid) . '=='));
  636. return substr($decoded, 0, 8) . '-' . substr($decoded, 8, 4) . '-' . substr($decoded, 12, 4) . '-' . substr($decoded, 16, 4) . '-' . substr($decoded, 20);
  637. }
  638. /**
  639. * Clean (erase) all output buffers and turn off output buffering.
  640. */
  641. final public static function ob_end_clean_all()
  642. {
  643. while(@ob_end_clean())
  644. ;
  645. }
  646. /**
  647. * Determine whether a value contains a valid serialized string, and optionally
  648. * return the unserialized result.
  649. *
  650. * @param any $value value to check
  651. * @param and &$decoded unserialized result (only valid if method returns true)
  652. * @return bool
  653. */
  654. final public static function is_serialized($value, &$decoded = NULL)
  655. {
  656. if (!is_string($value))
  657. {
  658. return false;
  659. }
  660. if ($value === 'b:0;')
  661. {
  662. $decoded = false;
  663. return true;
  664. }
  665. return (($decoded = @unserialize($value)) !== false);
  666. }
  667. /**
  668. * Determine whether a value contains a valid JSON-encoded string, and optionally
  669. * return the decoded result.
  670. *
  671. * @param any $value value to check
  672. * @param and &$decoded decoded result (only valid if method returns true)
  673. * @return bool
  674. */
  675. final public static function is_json($value, &$decoded = NULL)
  676. {
  677. if (!is_string($value))
  678. {
  679. return false;
  680. }
  681. return (($decoded = @json_decode($value)) !== NULL) || (json_last_error() === JSON_ERROR_NONE);
  682. }
  683. }
  684. /**
  685. * The SparkInflector class is used to convert strings to/from under_score and CamelCase.
  686. * This class may be used by core classes, application classes and plugs.
  687. * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
  688. *
  689. * @package Spark/Plug
  690. */
  691. // -----------------------------------------------------------------------------
  692. final class SparkInflector
  693. {
  694. /**
  695. * Convert an indentifier to readable text ("example_identifier" -> "Example Identifier").
  696. *
  697. * @param string $s
  698. * @return string
  699. */
  700. final public static function humanize($s)
  701. {
  702. return ucfirst(str_replace('_', ' ', $s));
  703. }
  704. /**
  705. * Convert readable text to identifier ("Example Identifier" -> "example_identifier").
  706. *
  707. * @param string $s
  708. * @return string
  709. */
  710. final public static function dehumanize($s)
  711. {
  712. return strtolower(str_replace(' ', '_', $s));
  713. }
  714. /**
  715. * Convert an indentifier to CamelCase ("example_identifier" -> "ExampleIdentifier").
  716. *
  717. * @param string $s
  718. * @return string
  719. */
  720. final public static function camelize($s)
  721. {
  722. return str_replace(' ', '', ucwords(str_replace('_', ' ', $s)));
  723. }
  724. /**
  725. * Convert an indentifier from CamelCase to underscores ("ExampleIdentifier" -> "example_identifier").
  726. *
  727. * @param string $s
  728. * @return string
  729. */
  730. final public static function decamelize($s)
  731. {
  732. return trim(strtolower(preg_replace(array('/([A-Z])([a-z])/', '/([a-z])([A-Z])/'), array('_$1$2', '$1_$2'), $s)), '_');
  733. }
  734. }
  735. /**
  736. * The SparkConfig class is used to store and retrieve configuration data.
  737. * This class may be used by core classes, application classes and plugs.
  738. * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
  739. *
  740. * @package Spark/Plug
  741. */
  742. // -----------------------------------------------------------------------------
  743. final class SparkConfig
  744. {
  745. private $_config; // key-value dictionary of configuration settings
  746. /**
  747. * Construct a new Config object.
  748. *
  749. * @param array $config: key-value dictionary of configuration values
  750. */
  751. public function __construct(&$config)
  752. {
  753. $this->_config =& $config;
  754. }
  755. /**
  756. * Retrieve a previously stored configuration item.
  757. * If the item is not found, return the provided default value (if any).
  758. *
  759. * @param string $key Name of configuration parameter to retrieve
  760. * @param string $default Default value to return if configuration parameter not found
  761. * @return any Value of configuration parameter if found, or default if not found
  762. */
  763. final public function get($key, $default = NULL)
  764. {
  765. return self::paramGet($this->_config, $key, $default);
  766. }
  767. /**
  768. * Store a new configuration item.
  769. *
  770. * @param string $key Name of configuration parameter to store
  771. * @param string $val Value to store
  772. */
  773. final public function set($key, $val)
  774. {
  775. $this->_config[$key] = $val;
  776. }
  777. /**
  778. * Retrieve a value from a parameter array.
  779. * If the item is not found, return the provided default value (if any).
  780. *
  781. * @param string $key Name of parameter to retrieve
  782. * @param string $default Default value to return if parameter not found
  783. * @return any Value of parameter if found, or default if not found
  784. */
  785. final public static function paramGet($arr, $key, $default = NULL)
  786. {
  787. return isset($arr[$key]) ? $arr[$key] : $default;
  788. }
  789. /**
  790. * Retrieve a (normalized) PHP configuration setting.
  791. *
  792. * @param string $key Name of configuration parameter to retrieve
  793. * @param string $default Default value to return if configuration parameter not found
  794. * @return string Value of configuration parameter if found, or default if not found
  795. */
  796. final public static function php_ini_get($key, $default = NULL)
  797. {
  798. switch (strtolower($val = ini_get($key)))
  799. {
  800. case '':
  801. return $default;
  802. case '0':
  803. case 'off':
  804. case 'false':
  805. case 'no':
  806. case 'none':
  807. return false;
  808. case '1':
  809. case 'on':
  810. case 'true':
  811. case 'yes':
  812. return true;
  813. default:
  814. return $val;
  815. }
  816. }
  817. }
  818. /**
  819. * The SparkObserver class is used to register observable events and their observers.
  820. * Once events and observers have been registered, events may be fired and all observers
  821. * registered for those events will be notified.
  822. * This class may be used by core classes, application classes and plugs.
  823. * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
  824. *
  825. * @package Spark/Plug
  826. */
  827. // -----------------------------------------------------------------------------
  828. final class SparkObserver
  829. {
  830. private $_events; // list of observable events
  831. /**
  832. * Construct a new SparkObserver object.
  833. *
  834. */
  835. public function __construct()
  836. {
  837. $this->_events = array();
  838. }
  839. /**
  840. * Register an observer for one or more events.
  841. *
  842. * An observer can be one of:
  843. *
  844. * a function name
  845. * a lambda function
  846. * an array: (class name, static method name)
  847. * an array: (object, method name)
  848. *
  849. * One or more events can be registered for each observer.
  850. * Events can be specified as either an array of event names or as a
  851. * comma-delimited string containing one or more event names.
  852. *
  853. * @param string $observer Called when registered events fires
  854. * @param string $events Event (or events) to observe
  855. */
  856. final public function observe($observer, $events)
  857. {
  858. if (!is_array($events))
  859. {
  860. $events = explode(',', str_replace(' ', '', $events));
  861. }
  862. // add the observer to the event list for each event it is observing
  863. foreach($events as $event)
  864. {
  865. $this->_events[$event][] = $observer;
  866. }
  867. }
  868. /**
  869. * Notify observers that an event has fired.
  870. * Observers will be passed the event name plus any additional args passed to
  871. * this method.
  872. *
  873. * @param mixed $events Event (or events) to fire
  874. * @param mixed $params1...$paramN variable argument list to pass to observer
  875. */
  876. final public function notify($events)
  877. {
  878. if (!is_array($events))
  879. {
  880. $events = explode(',', str_replace(' ', '', $events));
  881. }
  882. // A "composite event" is of the form "general:more_specific:most_specific",
  883. // a series of component events separated by colons in which each subsequent
  884. // component is more specific than the preceding component. For composite
  885. // events, we send multiple notifications, so listeners may listen on on as
  886. // specific a component as desired. For example, given composite event:
  887. // "site_changed:content:page_saved:edited"
  888. // we would send the event notification to listeners of the folowing four events:
  889. // "site_changed"
  890. // "site_changed:content"
  891. // "site_changed:content:page_saved"
  892. // "site_changed:content:page_saved:edited"
  893. //
  894. // Note that the specific event (in this case, "site_changed:content:page_saved:edited")
  895. // is always passed as the first argument to the listener's callback function.
  896. foreach ($events as $event)
  897. {
  898. $first = true;
  899. foreach (explode(':', $event) as $component)
  900. {
  901. if (!empty($component))
  902. {
  903. if ($first)
  904. {
  905. $notification = $component;
  906. $first = false;
  907. }
  908. else
  909. {
  910. $notification .= ':' . $component;
  911. }
  912. if (isset($this->_events[$notification]))
  913. {
  914. foreach ($this->_events[$notification] as $observer)
  915. {
  916. $args = func_get_args();
  917. $args[0] = $event;
  918. call_user_func_array($observer, $args);
  919. }
  920. }
  921. }
  922. }
  923. }
  924. }
  925. }
  926. /**
  927. * The Spark class is Spark/Plug's object factory. It loads plugs and constructs
  928. * object hierarchies on the fly when instantiating a new object.
  929. * This class may be used by core classes, application classes and plugs.
  930. * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
  931. *
  932. * @package Spark/Plug
  933. */
  934. // -----------------------------------------------------------------------------
  935. final class Spark
  936. {
  937. private $_spDir; // Spark/Plug directory
  938. private $_observer; // global observer
  939. private $_plugList; // list if installed plugs indexed by class name
  940. private $_plugMap; // maps class names to list of plugs that implement/extend that class name
  941. private $_plugCacheDir; // directory containing cached plugs
  942. private $_plugDirStack; // directories containing plugs
  943. // --------------------------------------------------------------------------
  944. /**
  945. * Construct a new Spark object, bootstrap the application, and "activate" plugs.
  946. * Only plugs that extend (derive directly from) the SparkPlug class need be specified
  947. * at this time. Additional plugs may be activated at any time by invoking the findPlugs()
  948. * method of this class.
  949. *
  950. * @param array $classes Array of names of plugs to "activate" (make available to the running application)
  951. * @param string|array $searchPaths Optional path(s) to search for plugs
  952. * @param string $cacheDir Optional path to plug cache directory
  953. */
  954. final public function __construct($classes = array(), $searchPaths = NULL, $cacheDir = NULL)
  955. {
  956. $this->_observer = new SparkObserver();
  957. $this->_plugList = array();
  958. $this->_plugMap = array();
  959. $this->_plugCacheDir = NULL;
  960. $this->_spDir = dirname(__FILE__);
  961. if (!empty($cacheDir))
  962. {
  963. $this->setPlugCacheDir($cacheDir);
  964. $this->_observer->observe(array($this, 'flushPlugCache'), 'Spark:cache:request_flush');
  965. }
  966. if (empty($searchPaths))
  967. {
  968. $searchPaths = array(NULL);
  969. }
  970. foreach ($searchPaths as $dir)
  971. {
  972. if (empty($dir))
  973. {
  974. $this->_plugDirStack[] = "{$this->_spDir}/plugs";
  975. }
  976. else
  977. {
  978. if ($dir[0] !== '/')
  979. {
  980. $dir = "{$this->_spDir}/{$dir}";
  981. }
  982. $this->_plugDirStack[] = $dir;
  983. }
  984. }
  985. $this->findPlugs($classes);
  986. // load the SparkPlug class and all its plugs
  987. $this->loadClass('SparkPlug');
  988. }
  989. // --------------------------------------------------------------------------
  990. /**
  991. * Return the global observer object
  992. *
  993. * @return object Observer object
  994. */
  995. final public function observer()
  996. {
  997. return $this->_observer;
  998. }
  999. // --------------------------------------------------------------------------
  1000. /**
  1001. * Initialize the Spark object and load plugs that extend core classes.
  1002. * This method is invoked at the bottom of this file.
  1003. *
  1004. */
  1005. final public function init()
  1006. {
  1007. // load all the core classes and their respective plugs
  1008. $this->loadClass('SparkModel');
  1009. $this->loadClass('SparkView');
  1010. $this->loadClass('SparkController');
  1011. $this->loadClass('SparkApplication');
  1012. }
  1013. // --------------------------------------------------------------------------
  1014. /**
  1015. * Returns the path to the plug cache directory.
  1016. *
  1017. * @return string Path to plug cache directory
  1018. */
  1019. // --------------------------------------------------------------------------
  1020. public function getPlugCacheDir()
  1021. {
  1022. return $this->_plugCacheDir;
  1023. }
  1024. // --------------------------------------------------------------------------
  1025. /**
  1026. * Call this method to change the plug cache directory for subsequent plug cache operations.
  1027. * Used to dynamically change where code is cached based on caller's criteria (eg. host name).
  1028. * Should be called as early as possible. Note that some classes may have already been cached
  1029. * by the time this method is called.
  1030. *
  1031. * @param string $cacheDir Path to new plug cache directory
  1032. *
  1033. * @return string Previous cache directory path
  1034. */
  1035. // --------------------------------------------------------------------------
  1036. public function setPlugCacheDir($cacheDir)
  1037. {
  1038. $oldDir = $this->_plugCacheDir;
  1039. $this->_plugCacheDir = $cacheDir;
  1040. if ($this->_plugCacheDir[0] !== '/')
  1041. {
  1042. $this->_plugCacheDir = "{$this->_spDir}/{$this->_plugCacheDir}";
  1043. }
  1044. return $oldDir;
  1045. }
  1046. // --------------------------------------------------------------------------
  1047. /**
  1048. * Call this method to flush the plug code cache.
  1049. * Not usually called directly, as it can be invoked via the "Spark:cache:request_flush"
  1050. * notification.
  1051. */
  1052. // --------------------------------------------------------------------------
  1053. public function flushPlugCache()
  1054. {
  1055. if (!empty($this->_plugCacheDir))
  1056. {
  1057. $iter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_plugCacheDir), RecursiveIteratorIterator::CHILD_FIRST);
  1058. foreach ($iter as $file)
  1059. {
  1060. $filePath = $file->getPathname();
  1061. if ($file->isDir())
  1062. {
  1063. $fileName = $file->getFilename();
  1064. if ($fileName !== '.' && $fileName !== '..')
  1065. {
  1066. rmdir($filePath);
  1067. }
  1068. }
  1069. else
  1070. {
  1071. unlink($filePath);
  1072. }
  1073. }
  1074. $this->_observer->notify('Spark:cache:flush');
  1075. }
  1076. }
  1077. // --------------------------------------------------------------------------
  1078. /**
  1079. * Call this method to make additional plugs available to be loaded.
  1080. * Subsequent invocations of manufacture() will consider these additional plugs when
  1081. * determining which plugs to load. If $searchPaths parameter is provided, it will
  1082. * be used instead of the main plug directory for the scope of this invocation only.
  1083. * If $callback is provided, it will be called for each plug and passed the plug as a
  1084. * parameter.
  1085. *
  1086. * @param array $classes Array of plug names to make available
  1087. * @param string|array $searchPaths Optional path(s) to search for plugs
  1088. * @param string $callback Callback (optional)
  1089. */
  1090. final public function findPlugs($classes, $searchPaths = NULL, $callback = NULL)
  1091. {
  1092. if (!empty($classes))
  1093. {
  1094. if (empty($searchPaths))
  1095. {
  1096. $searchPaths = $this->_plugDirStack;
  1097. }
  1098. elseif (!is_array($searchPaths))
  1099. {
  1100. $searchPaths = array($searchPaths);
  1101. }
  1102. foreach ($classes as $class)
  1103. {
  1104. // allow nested plug directories
  1105. if ($subPath = (strpos($class, '/') === false) ? '' : '/'.dirName($class))
  1106. {
  1107. $class = basename($class);
  1108. }
  1109. foreach ($searchPaths as $plugDir)
  1110. {
  1111. $dirName = $plugDir . $subPath . '/' . SparkInflector::decamelize($class);
  1112. if (file_exists($dirName))
  1113. {
  1114. break;
  1115. }
  1116. }
  1117. // load plug's manifest
  1118. unset($plug);
  1119. require($dirName . '/manifest.php');
  1120. // add plug to plug map
  1121. if (isset($plug))
  1122. {
  1123. $plugs = $plug['plugs'];
  1124. unset($plug['plugs']);
  1125. foreach ($plugs as $nextPlug)
  1126. {
  1127. if ($callback)
  1128. {
  1129. $nextPlug = array_merge($plug, $nextPlug); // make sure callback has all the plug info
  1130. // Note: We do not use call_user_func() here because it does not allow pass-by-reference
  1131. if (is_string($callback))
  1132. {
  1133. if (!$callback($nextPlug))
  1134. {
  1135. continue;
  1136. }
  1137. }
  1138. elseif (is_array($callback))
  1139. {
  1140. if (!$callback[0]->$callback[1]($nextPlug))
  1141. {
  1142. continue;
  1143. }
  1144. }
  1145. }
  1146. $name = $nextPlug['name'];
  1147. // if an explicit path is provided, use it, otherwise deduce a path from the plug's name
  1148. if ($path = @$nextPlug['path'])
  1149. {
  1150. // explicit path can be absolute, relative to plugs directory, or relative to plug's own directory
  1151. if ($path[0] != '/')
  1152. {
  1153. if (!strncasecmp($path, 'plugs/', 6))
  1154. {
  1155. $path = $plugDir . '/' . substr($path, 6);
  1156. }
  1157. else
  1158. {
  1159. $path = $dirName . '/' . $path;
  1160. }
  1161. }
  1162. }
  1163. else
  1164. {
  1165. $path = $dirName . '/' . SparkInflector::decamelize($name) . '.php';
  1166. }
  1167. // plug can be one of two types:
  1168. //
  1169. // 1. an extension plug that augments an existing class,
  1170. // or
  1171. // 2. a base plug that simply provides some new service
  1172. if ($extends = @$nextPlug['extends'])
  1173. {
  1174. // extension plugs can declare a preferred load order to help avoid conflicts
  1175. $order = isset($nextPlug['order']) ? max(1, min(100, $nextPlug['order'])) : 50;
  1176. }
  1177. else
  1178. {
  1179. $extends = $name;
  1180. $order = 0;
  1181. }
  1182. $plugInfo = array('name'=>$name, 'extends'=>$extends, 'order'=>$order, 'file'=>$path);
  1183. if (!empty($nextPlug['requires']))
  1184. {
  1185. if (is_string($requires = $nextPlug['requires']))
  1186. {
  1187. $requires = array_map('trim', explode(',', $requires));
  1188. }
  1189. $plugInfo['requires'] = $requires;
  1190. }
  1191. if (($order === 0) && isset($nextPlug['base_class']))
  1192. {
  1193. $plugInfo['base_class'] = $nextPlug['base_class'];
  1194. }
  1195. $this->_plugList[$name] =& $plugInfo;
  1196. $this->_plugMap[$extends][$order][] =& $plugInfo;
  1197. unset($plugInfo);
  1198. }
  1199. unset($plug);
  1200. }
  1201. }
  1202. // sort plugs according to load order
  1203. foreach(array_keys($this->_plugMap) as $key)
  1204. {
  1205. ksort($this->_plugMap[$key]);
  1206. }
  1207. }
  1208. }
  1209. // --------------------------------------------------------------------------
  1210. /**
  1211. * Call this method to make an additional plug available to be loaded.
  1212. * Subsequent invocations of manufacture() will consider this plug when
  1213. * determining which plugs to load. Unlike findPlugs(), this method can
  1214. * accept a callback to load the plug's code, rather than a file.
  1215. *
  1216. * @param array $plug:
  1217. * (
  1218. * string 'name' => name of plug to add,
  1219. * string 'requires' => name(s) of class(es) (if any) required by this plug,
  1220. * string 'extends' => name of class (if any) being extended by this plug,
  1221. * int 'order' => load order for this plug
  1222. * string 'file' => location of plug's code file, OR
  1223. * mixed 'callback' => callback to create the plug,
  1224. */
  1225. final public function addPlug($plug)
  1226. {
  1227. if (!empty($plug['requires']))
  1228. {
  1229. if (is_string($requires = $plug['requires']))
  1230. {
  1231. $requires = array_map('trim', explode(',', $requires));
  1232. }
  1233. $plug['requires'] = $requires;
  1234. }
  1235. $plug['order'] = !empty($plug['extends']) ? max(1, min(100, !empty($plug['order']) ? $plug['order'] : 100)) : 0;
  1236. if (empty($plug['extends']))
  1237. {
  1238. $plug['extends'] = $plug['name'];
  1239. }
  1240. $this->_plugList[$plug['name']] =& $plug;
  1241. $this->_plugMap[$plug['extends']][$plug['order']][] =& $plug;
  1242. ksort($this->_plugMap[$plug['extends']]);
  1243. }
  1244. // --------------------------------------------------------------------------
  1245. /**
  1246. * Call this method to retrieve information about an installed plug.
  1247. *
  1248. * @param string $name: => name of plug
  1249. * @return array of plug info
  1250. */
  1251. final public function getPlug($name)
  1252. {
  1253. return @$this->_plugList[$name];
  1254. }
  1255. // --------------------------------------------------------------------------
  1256. /**
  1257. * Call this method to retrieve information about all installed plugs that extend a specified plug.
  1258. *
  1259. * @param string $name: => name of extended plug
  1260. * @return array of plug infos
  1261. */
  1262. final public function getExtensions($name)
  1263. {
  1264. if (empty($this->_plugMap[$name]))
  1265. {
  1266. return NULL;
  1267. }
  1268. $extensions = array();
  1269. foreach ($this->_plugMap[$name] as $plugList)
  1270. {
  1271. $extensions = array_merge($extensions, $plugList);
  1272. }
  1273. return $extensions;
  1274. }
  1275. // --------------------------------------------------------------------------
  1276. /**
  1277. * Dynamically build a class hierarchy and instantiate a new object.
  1278. * This method is used to create any object that you want to be extensible.
  1279. *
  1280. * For example, any object derived from SparkPlug may call:
  1281. *
  1282. * $myObject = $this->factory->manufacture('MyClass');
  1283. *
  1284. * and $myObject will be created and will include the functionality provided by
  1285. * all plugs that extend MyClass.
  1286. *
  1287. * However, if instead you call:
  1288. *
  1289. * $myObject = new MyClass;
  1290. *
  1291. * you will still create a MyClass object, but it will not include any plug functionality.
  1292. *
  1293. * @param string $class Name of class to instantiate
  1294. * @return object
  1295. */
  1296. final public function manufacture($class)
  1297. {
  1298. $this->_observer->notify('Spark:manufacture:' . $class, $class);
  1299. // load the class and all its extension plugs
  1300. $class = $this->loadClass($class);
  1301. // get parameters for constructor (remove the class name from start of args)
  1302. $params = func_get_args();
  1303. array_shift($params);
  1304. // return call_user_func_array(array($class, '__construct'), $params);
  1305. // can't call a constructor via call_user_func_array() because it is not a static method
  1306. switch ($c = count($params))
  1307. {
  1308. case 0:
  1309. return new $class();
  1310. case 1:
  1311. return new $class($params[0]);
  1312. case 2:
  1313. return new $class($params[0], $params[1]);
  1314. case 3:
  1315. return new $class($params[0], $params[1], $params[2]);
  1316. case 4:
  1317. return new $class($params[0], $params[1], $params[2], $params[3]);
  1318. case 5:
  1319. return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
  1320. case 6:
  1321. return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5]);
  1322. case 7:
  1323. return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6]);
  1324. case 8:
  1325. return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7]);
  1326. case 9:
  1327. return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7], $params[8]);
  1328. case 10:
  1329. return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7], $params[8], $params[9]);
  1330. default:
  1331. throw new SparkException('too many arguments (' . $c . ') passed to constructor (max 10)');
  1332. }
  1333. }
  1334. // --------------------------------------------------------------------------
  1335. /**
  1336. * Call this method to load a class and all its extension plugs.
  1337. * Optionally cache the class to the plug cache directory.
  1338. *
  1339. * @param string $class Name of class to load
  1340. * @param boolean $abstract True if this is an abstract base class
  1341. * @return string Name of instantiated class (alternate implementation may change class name)
  1342. */
  1343. final public function loadClass($class, $abstract = false)
  1344. {
  1345. // no need to load a class more than once
  1346. if (class_exists($class, false))
  1347. {
  1348. return $class;
  1349. }
  1350. // If caching is enabled, check the cache directory for this class.
  1351. if (!empty($this->_plugCacheDir))
  1352. {
  1353. $cacheFile = $this->_plugCacheDir . '/' . $class . '.php';
  1354. if ((@include $cacheFile) !== false)
  1355. {
  1356. return $class;
  1357. }
  1358. }
  1359. // Extensible classes are always declared with an underscore prefix on the name,
  1360. // which allows us to use the class as a base class for plugs, while leaving the
  1361. // ultimate class name (without the underscore) free for dynamic creation once
  1362. // all the plugs have been loaded.
  1363. $baseClass = '_' . $class;
  1364. // if the base class does not exist, it is a plug itself and needs to be loaded
  1365. if (isset($this->_plugMap[$class])) // any plugs want to extend this class?
  1366. {
  1367. foreach ($this->_plugMap[$class] as $plugs)
  1368. {
  1369. foreach ($plugs as $plug)
  1370. {
  1371. $plugName = $plug['name']; // plug's name
  1372. $plugOrder = $plug['order']; // plug's load order
  1373. $plugCode = false; // plug's code
  1374. // take care of dependencies
  1375. foreach ((array)@$plug['requires'] as $dependency)
  1376. {
  1377. $this->loadClass($dependency);
  1378. }
  1379. // To allow easier integration with external class libraries, the manifest may
  1380. // specify alternate class names for base classes.
  1381. if (($plugOrder == 0) && isset($plug['base_class']))
  1382. {
  1383. $baseClass = $plug['base_class'];
  1384. }
  1385. // load the plug's code
  1386. if ($plugFile = @$plug['file'])
  1387. {
  1388. $plugCode = file_get_contents($plugFile);
  1389. }
  1390. elseif ($callback = @$plug['callback'])
  1391. {
  1392. // Note: We do not use call_user_func() here because it does not allow pass-by-reference
  1393. if (is_string($callback))
  1394. {
  1395. $plugCode = $callback($plug);
  1396. }
  1397. elseif (is_array($callback))
  1398. {
  1399. $plugCode = $callback[0]->$callback[1]($plug);
  1400. }
  1401. }
  1402. if ($plugCode !== false)
  1403. {
  1404. $plugCode = preg_replace("/\s*<\?(php)?/", '', $plugCode, 1);
  1405. // Here's the tricky bit... We dynamically rewrite the plug's class definition to build our
  1406. // class hierarchy. (Self-modifying code - oh my!)
  1407. // So, each plug is loaded in turn and serves as the base class for the next plug to be loaded.
  1408. // Performance of this mechanism actually turns out to be quite respectable. The major downside
  1409. // is that opcode cachers will not be able to cache the bytecode of plugs loaded in this manner,
  1410. // (which is generally true of eval'd code). However, this drawback may be mitigated by enabling
  1411. // plug caching, which writes the entire class inheritance chain to a single class file.
  1412. if ($plugOrder != 0) // not a base class?
  1413. {
  1414. $firstClass = isset($plug['first_class']) ? $plug['first_class'] : $plugName;
  1415. $plugCode = preg_replace("/class\s+{$firstClass}\s+extends\s+(?:{$class}|{$baseClass})/", "class {$firstClass} extends {$baseClass}", $plugCode, 1, $count);
  1416. if ($count !== 1) // something fishy here, best abort
  1417. {
  1418. throw new SparkException('plug err: Could not load plug "' . ($plugFile ? $plugFile : $plugName) . '"');
  1419. }
  1420. }
  1421. // create cache file if caching enabled
  1422. if (!isset($cacheFileHandle) && !empty($this->_plugCacheDir))
  1423. {
  1424. if (($cacheFileHandle = @fopen($cacheFile, 'w')) !== false)
  1425. {
  1426. if (fwrite($cacheFileHandle, "<?php\n") === false)
  1427. {
  1428. throw new SparkException('plug err: Could not write plug cache file "' . $cacheFile . '"');
  1429. }
  1430. }
  1431. }
  1432. if (!empty($cacheFileHandle))
  1433. {
  1434. if (fwrite($cacheFileHandle, $plugCode) === false)
  1435. {
  1436. throw new SparkException('plug err: Could not write plug cache file "' . $cacheFile . '"');
  1437. }
  1438. }
  1439. elseif ($this->loadPlug($plugCode) === false)
  1440. {
  1441. throw new SparkException('plug err: Could not load plug "' . ($plugFile ? $plugFile : $plugName) . '"');
  1442. }
  1443. // plug successfully loaded and will serve as the base class for the next to load (if any)
  1444. if ($plugOrder != 0) // not a base class?
  1445. {
  1446. $baseClass = $plugName;
  1447. }
  1448. }
  1449. }
  1450. }
  1451. }
  1452. // Finally, dynamically generate the requested class as a simple wrapper class.
  1453. // This makes the class name a legitimate object in the PHP namespace so it can be referred to by
  1454. // other functions, etc.
  1455. $plugCode = "\nif (!class_exists('{$class}', false)) { " . ($abstract ? 'abstract ' : '') . "class {$class} extends {$baseClass} {} }\n";
  1456. if (!empty($cacheFileHandle))
  1457. {
  1458. if (fwrite($cacheFileHandle, $plugCode) === false)
  1459. {
  1460. throw new SparkException('plug err: Could not write plug cache file "' . $cacheFile . '"');
  1461. }
  1462. fclose($cacheFileHandle);
  1463. include $cacheFile;
  1464. }
  1465. else
  1466. {
  1467. if (!class_exists($baseClass, false))
  1468. {
  1469. throw new SparkException('loader err: Could not load class "' . $baseClass . '"');
  1470. }
  1471. $this->loadPlug($plugCode);
  1472. }
  1473. return $class;
  1474. }
  1475. // --------------------------------------------------------------------------
  1476. /**
  1477. * Private method to isolate the loading of the plug's code.
  1478. *
  1479. * @param string $plugCode Plug code to load into PHP interpreter
  1480. * @return mixed Result of loading the plug's code
  1481. */
  1482. final private function loadPlug($plugCode)
  1483. {
  1484. return eval($plugCode);
  1485. }
  1486. }
  1487. /**
  1488. * The _SparkPlug (SparkPlug) class is Spark/Plug's universal base object class. Any
  1489. * class that wants access to the application object and/or the Spark factory object
  1490. * should derive from this class or one of its descendants. This class is not meant to
  1491. * be instantiated directly. When specifying this class as a base class in your class
  1492. * definition, be sure to extend SparkPlug (not _SparkPlug). Otherwise, your derived class
  1493. * will not inherit the capabilities of any plugs that extend this class.
  1494. * This class may be used by core classes, application classes and plugs.
  1495. *
  1496. * @package Spark/Plug
  1497. */
  1498. // -----------------------------------------------------------------------------
  1499. abstract class _SparkPlug
  1500. {
  1501. public $app; // convenience reference to global application object
  1502. public $config; // convenience reference to application configuration store
  1503. public $factory; // convenience reference back global object factory
  1504. public $observer; // convenience reference to global observer
  1505. // --------------------------------------------------------------------------
  1506. /**
  1507. * Construct a new SparkPlug object. Make key application objects available to all
  1508. * derived classes.
  1509. */
  1510. public function __construct()
  1511. {
  1512. $this->app = SparkApplication::instance();
  1513. $this->config = $this->app->config();
  1514. $this->factory = $this->app->factory();
  1515. $this->observer = $this->factory->observer();
  1516. }
  1517. /**
  1518. * Create a new model object. Calls through to the application's newModel() method.
  1519. *
  1520. * @param string $name Name of model class to instatiate
  1521. * @return object Model object
  1522. */
  1523. public function newModel($name, $params = NULL)
  1524. {
  1525. return $this->app->newModel($name, $params);
  1526. }
  1527. /**
  1528. * Create a full url to the specified (static) path. Calls through to the application's urlToStatic() method.
  1529. *
  1530. * @param string $path Path to which to create full URL
  1531. * @param bool $withHost Whether to include host in URL
  1532. * @param bool $withScheme Whether to include scheme in URL
  1533. * @param bool $secure Whether to generate a secure URL (true), a non-secure URL (false), or default (NULL)
  1534. * @return string URL
  1535. */
  1536. public function urlToStatic($path, $withHost = false, $withScheme = true, $secure = NULL)
  1537. {
  1538. return $this->app->urlToStatic($path, $withHost, $withScheme, $secure);
  1539. }
  1540. /**
  1541. * Create a full url to the specified (dynamic) path. Calls through to the application's urlTo() method.
  1542. *
  1543. * @param string $path Path to which to create full URL
  1544. * @param bool $withHost Whether to include host in URL
  1545. * @param bool $withScheme Whether to include scheme in URL
  1546. * @param bool $secure Whether to generate a secure URL (true), a non-secure URL (false), or default (NULL)
  1547. * @return string URL
  1548. */
  1549. public function urlTo($path, $withHost = false, $withScheme = true, $secure = NULL)
  1550. {
  1551. return $this->app->urlTo($path, $withHost, $withScheme, $secure);
  1552. }
  1553. /**
  1554. * Create a full url to the specified (static) secure path. Calls through to the application's urlToStaticSecure() method.
  1555. *
  1556. * @param string $path Path to which to create secure URL
  1557. * @return string URL
  1558. */
  1559. public function urlToStaticSecure($path)
  1560. {
  1561. return $this->app->urlToStaticSecure($path);
  1562. }
  1563. /**
  1564. * Create a full url to the specified (dynamic) secure path. Calls through to the application's urlToSecure() method.
  1565. *
  1566. * @param string $path Path to which to create secure URL
  1567. * @return string URL
  1568. */
  1569. public function urlToSecure($path)
  1570. {
  1571. return $this->app->urlToSecure($path);
  1572. }
  1573. /**
  1574. * Create a redirect to the specified (static) path. Calls through to the application's urlToStatic() method.
  1575. *
  1576. * @param string $path Path to which to create full URL to redirect to
  1577. * @param bool $secure Whether…

Large files files are truncated, but you can click here to view the full file