/sparkplug/sparkplug.php
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
- <?php
- /*
- Copyright 2009-2012 Sam Weiss
- All Rights Reserved.
- This file is part of Spark/Plug.
- Spark/Plug is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- if (!defined('spark/plug'))
- {
- header('HTTP/1.1 403 Forbidden');
- 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>');
- }
- define('spark_plug_version', '1.1.0');
- /**
- * The SparkException class is our custom exception class.
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- class SparkException extends Exception
- {
- private $_vars;
-
- /**
- * Construct a new exception.
- *
- * @param string $message
- * @param int $code
- * @return SparkException object
- */
- public function __construct($message, $code = 0, $vars = NULL)
- {
- parent::__construct($message, $code);
- $this->_vars = empty($vars) ? array() : $vars;
- }
- /**
- * Return vars associated with this exception.
- *
- * @return array
- */
- public function vars()
- {
- return $this->_vars;
- }
- /**
- * Return named var associated with this exception.
- *
- * @param string $name
- * @return mixed
- */
- public function getVar($name, $default = NULL)
- {
- return isset($this->_vars[$name]) ? $this->_vars[$name] : $default;
- }
- }
- /**
- * The SparkHTTPException class is used to automatically convert HTTP status codes to exceptions.
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- class SparkHTTPException extends SparkException
- {
- private $_httpStatusCode;
- private $_httpStatusText;
-
- /**
- * Convert HTTP status code to equivalent exception.
- *
- * @param string $message
- * @param int $code
- * @return SparkException object
- */
- public function __construct($httpStatusCode, $httpStatusText, $message = '', $vars = '')
- {
- parent::__construct($message, 0, $vars);
- $this->_httpStatusCode = $httpStatusCode;
- $this->_httpStatusText = $httpStatusText;
- }
- /**
- * Return HTTP status code associated with this exception.
- *
- * @return int
- */
- public function getHTTPStatusCode()
- {
- return $this->_httpStatusCode;
- }
-
- /**
- * Return HTTP status text associated with this exception.
- *
- * @return string
- */
- public function getHTTPStatusText()
- {
- return $this->_httpStatusText;
- }
-
- /**
- * Return HTTP status messages associated with this exception.
- * Suitable for use in HTTP header.
- *
- * @return string
- */
- public function getHTTPStatus()
- {
- return $this->_httpStatusCode . ' ' . $this->_httpStatusText;
- }
- }
- class SparkHTTPException_BadRequest extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(400, 'Bad Request', $message, $vars);
- }
- }
- class SparkHTTPException_Unauthorized extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(401, 'Unauthorized', $message, $vars);
- }
- }
- class SparkHTTPException_Forbidden extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- isset($message) || $message = 'Permission denied.';
- parent::__construct(403, 'Forbidden', $message, $vars);
- }
- }
- class SparkHTTPException_NotFound extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- isset($message) || $message = 'The page you requested was not found.';
- parent::__construct(404, 'Not Found', $message, $vars);
- }
- }
- class SparkHTTPException_MethodNotAllowed extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(405, 'Method Not Allowed', $message, $vars);
- }
- }
- class SparkHTTPException_Conflict extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(409, 'Conflict', $message, $vars);
- }
- }
- class SparkHTTPException_Gone extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(410, 'Gone', $message, $vars);
- }
- }
- class SparkHTTPException_LengthRequired extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(411, 'Length Required', $message, $vars);
- }
- }
- class SparkHTTPException_RequestEntityTooLarge extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(413, 'Request Entity Too Large', $message, $vars);
- }
- }
- class SparkHTTPException_RequestURITooLong extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(414, 'Request-URI Too Long', $message, $vars);
- }
- }
- class SparkHTTPException_UnsupportedMediaType extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(415, 'Unsupported Media Type', $message, $vars);
- }
- }
- class SparkHTTPException_InternalServerError extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- isset($message) || $message = 'Your request could not be processed.';
- parent::__construct(500, 'Internal Server Error', $message, $vars);
- }
- }
- class SparkHTTPException_NotImplemented extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(501, 'Not Implemented', $message, $vars);
- }
- }
- class SparkHTTPException_ServiceUnavailable extends SparkHTTPException
- {
- public function __construct($message = NULL, $vars = NULL)
- {
- parent::__construct(503, 'Service Unavailable', $message, $vars);
- }
- }
- /**
- * The SparkPHPException class is used to automatically convert PHP errors to exceptions.
- * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- final class SparkPHPException
- {
- /**
- * Convert PHP error to equivalent exception.
- *
- * @param int $code
- * @param string $message
- * @param string $file
- * @param int $line
- * @throw ErrorException
- */
- public static function errorHandler($code, $message, $file, $line)
- {
- throw new ErrorException($message, 0, $code, $file, $line);
- }
- }
- /**
- * The SparkUtil class contains some utility functions commonly needed by web apps.
- * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- final class SparkUtil
- {
- const kRequestMethod_HEAD = 1;
- const kRequestMethod_GET = 2;
- const kRequestMethod_POST = 3;
- const kRequestMethod_PUT = 4;
- const kRequestMethod_DELETE = 5;
- const kRequestMethod_OPTIONS = 6;
- private static $_http_methods = array
- (
- 'head' => self::kRequestMethod_HEAD,
- 'get' => self::kRequestMethod_GET,
- 'post' => self::kRequestMethod_POST,
- 'put' => self::kRequestMethod_PUT,
- 'delete' => self::kRequestMethod_DELETE,
- 'options' => self::kRequestMethod_OPTIONS,
- );
-
- /**
- * Return document root.
- *
- * @return string
- */
- final public static function doc_root()
- {
- return $_SERVER['DOCUMENT_ROOT'];
- }
- /**
- * Return whether this page was accessed securely.
- *
- * @return boolean
- */
- final public static function is_https()
- {
- return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
- }
- /**
- * Return scheme string.
- *
- * @return string
- */
- final public static function scheme()
- {
- return self::is_https() ? 'https://' : 'http://';
- }
- /**
- * Return host string.
- *
- * @return string
- */
- final public static function host()
- {
- return $_SERVER['HTTP_HOST'];
- }
- /**
- * Return server name string.
- *
- * @return string
- */
- final public static function server_name()
- {
- return $_SERVER['SERVER_NAME'];
- }
- /**
- * Return server ip address string.
- *
- * @return string
- */
- final public static function server_addr()
- {
- return $_SERVER['SERVER_ADDR'];
- }
- /**
- * Return script name.
- *
- * @return string
- */
- final public static function script_name()
- {
- return $_SERVER['SCRIPT_NAME'];
- }
- /**
- * Return original request URI.
- *
- * @return string
- */
- final public static function request_uri()
- {
- return $_SERVER['REQUEST_URI'];
- }
- /**
- * Return original request URI minus any query parameters.
- *
- * @return string
- */
- final public static function request_uri_base()
- {
- return self::remove_query_from_url(self::request_uri());
- }
- /**
- * Return original request URL.
- *
- * @return string
- */
- final public static function self_url()
- {
- if (($host = self::host()) === '')
- {
- $host = self::server_name();
- }
- return self::scheme() . $host . self::request_uri();
- }
- /**
- * Return original request method.
- *
- * @return string
- */
- final public static function request_method()
- {
- return !empty($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : '';
- }
- /**
- * Return original query string.
- *
- * @return string
- */
- final public static function query_string()
- {
- return !empty($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
- }
- /**
- * Return user's browser agent string.
- *
- * @return string
- */
- final public static function user_agent()
- {
- return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
- }
- /**
- * Return user's IP address.
- *
- * @return string
- */
- final public static function remote_ip()
- {
- return $_SERVER['REMOTE_ADDR'];
- }
-
- /**
- * Return referring url.
- *
- * @return string
- */
- final public static function referrer_url()
- {
- return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
- }
-
- /**
- * Return referring url.
- *
- * @return string
- */
- final public static function get_http_method($method = NULL)
- {
- if (!$method)
- {
- $method = self::request_method();
- }
- if ($method = @self::$_http_methods[strtolower($method)])
- {
- return $method;
- }
-
- return NULL;
- }
-
- /**
- * Return boolean indicating whether the current request was made via ajax.
- *
- * @return boolean
- */
- final public static function is_ajax_request()
- {
- return (strtolower(@$_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
- }
-
- /**
- * Return array of request headers.
- *
- * @return array
- */
- final public static function request_headers()
- {
- if (!function_exists('apache_request_headers'))
- {
- function apache_request_headers()
- {
- $headers = array();
- foreach($_SERVER as $key => $value)
- {
- if (substr_compare($key, 'HTTP_', 0, 5) === 0)
- {
- $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key,5)))));
- $headers[$key] = $value;
- }
- }
- return $headers;
- }
- }
-
- return apache_request_headers();
- }
-
- /**
- * Check if two IP addresses match or are similar.
- *
- * @param string $ip1
- * @param string $ip2
- * @return boolean
- */
- final public static function match_ip($ip1, $ip2, $matchOctets = 4)
- {
- // full IP match?
-
- if ($ip1 == $ip2)
- {
- return true;
- }
-
- // entire IP address did not match (probably a pesky proxy server)
- // break the address into octets
-
- $octets1 = explode('.' , $ip1, 4);
- $octets2 = explode('.' , $ip2, 4);
-
- for ($match = 0; $match < $matchOctets; ++$match)
- {
- if ($octets1[$match] != $octets2[$match])
- {
- return false;
- }
- }
-
- return true;
- }
- /**
- * Check for integer value, and force to type integer if true.
- *
- * @param any &$val
- * @return boolean
- */
- final public static function valid_int(&$val)
- {
- if (is_int($val))
- {
- return true;
- }
- if (preg_match('/^[-+]?\d+$/', $val))
- {
- $val = (int)$val;
- return true;
- }
- return false;
- }
- /**
- * Check if URL is valid.
- *
- * httpurl = https?://{hostport}(/{hpath}(\?{search})?)?
- * hostport = host(:{port})?
- * host = {hostname}|{hostnumber}
- * hostname = ({domainlabel}\.)*{toplabel}
- * domainlabel = {alphadigit}(({alphadigit}|\-)*{alphadigit})?
- * toplabel = {alpha}(({alphadigit}|\-)*{alphadigit})?
- * hostnumber = {digits}\.{digits}\.{digits}\.{digits}
- * port = {digits}
- * hpath = {hsegment}(/{hsegment})*
- * hsegment = ({uchar}|[;:@&=])*
- * search = ({uchar}|[;:@&=])*
- * uchar = {unreserved}|{escape}
- * unreserved = {alphadigit}|{safe}|{extra}
- * escape = %{hex}{hex}
- * safe = [\$\-\_\.\+]
- * extra = [\!\*\'\(\)\,]
- * hex = [0-9a-f]
- * alpha = [a-z]
- * digit = [0-9]
- * alphadigit = [a-z0-9]
- *
- * hsegment = (?:[a-z0-9\$\-\_\.\+\!\*\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*
- *
- * 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);
- *
- *
- * @param string $url
- * @return boolean
- */
- final public static function valid_url($url, $requireScheme = true)
- {
- 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;
- }
- /**
- * Check if URL path is valid.
- *
- * @param string $url
- * @return boolean
- */
- final public static function valid_url_path($url)
- {
- return preg_match('#^(?:/(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)*(?:\?(?:[a-z0-9\$\-\_\.\+\!\*\\\'\(\)\,\;\:\@\&\=\~]|(?:%[0-9a-f]{2}))*)?$#i', $url) ? true : false;
- }
- /**
- * 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).
- *
- * @param string $email
- * @return boolean
- */
- final public static function valid_email($email)
- {
- return preg_match('/^([[:alnum:]!#$%&\'*+-\/=?^_`{|}.])+@[[:alnum:]-]+(\.[[:alnum:]-]+)*$/', $email) ? true : false;
- }
- /**
- * Extract scheme and host from url.
- *
- * @param string $url
- * @return string
- */
- final public static function extract_scheme_host_from_url($url)
- {
- return preg_replace('#^(https?://[^/]+)(.*)$#', '$1', $url);
- }
- /**
- * Extract host from url.
- *
- * @param string $url
- * @return string
- */
- final public static function extract_host_from_url($url)
- {
- return preg_replace('#^https?://([^/]+)(.*)$#', '$1', $url);
- }
- /**
- * Remove query string from url.
- *
- * @param string $url
- * @return string
- */
- final public static function remove_query_from_url($url)
- {
- return preg_replace('/\?.*$/', '', $url);
- }
- /**
- * Create a nice big random string.
- *
- * @return string
- */
- final public static function make_nonce($length = 32)
- {
- $nonce = '';
- do
- {
- $nonce .= sha1(uniqid(mt_rand(), true));
- } while (strlen($nonce) < $length);
- return substr($nonce, 0, $length);
- }
- /**
- * Create a version 4 random UUID.
- *
- * @return string
- */
- final public static function make_uuid($canonical = true)
- {
- $format = $canonical ? '%04x%04x-%04x-%04x-%04x-%04x%04x%04x' : '%04x%04x%04x%04x%04x%04x%04x%04x';
- return sprintf
- (
- $format,
- mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
- mt_rand(0, 0x0fff) | 0x4000,
- mt_rand(0, 0x3fff) | 0x8000,
- mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
- );
- }
- /**
- * Encode a UUID into smaller ASCII string.
- *
- * @param string $uuid
- * @return string
- */
- final public static function encode_uuid($uuid)
- {
- return str_replace(array('/','+'), array('_','-'), substr(base64_encode(pack('H*', str_replace('-', '', $uuid))), 0, 22));
- }
-
- /**
- * Decode an encoded UUID.
- *
- * @param string $uuid
- * @return string
- */
- final public static function decode_uuid($uuid)
- {
- $decoded = bin2hex(base64_decode(str_replace(array('_','-'), array('/','+'), $uuid) . '=='));
- return substr($decoded, 0, 8) . '-' . substr($decoded, 8, 4) . '-' . substr($decoded, 12, 4) . '-' . substr($decoded, 16, 4) . '-' . substr($decoded, 20);
- }
-
- /**
- * Clean (erase) all output buffers and turn off output buffering.
- */
- final public static function ob_end_clean_all()
- {
- while(@ob_end_clean())
- ;
- }
-
- /**
- * Determine whether a value contains a valid serialized string, and optionally
- * return the unserialized result.
- *
- * @param any $value value to check
- * @param and &$decoded unserialized result (only valid if method returns true)
- * @return bool
- */
- final public static function is_serialized($value, &$decoded = NULL)
- {
- if (!is_string($value))
- {
- return false;
- }
-
- if ($value === 'b:0;')
- {
- $decoded = false;
- return true;
- }
-
- return (($decoded = @unserialize($value)) !== false);
- }
-
- /**
- * Determine whether a value contains a valid JSON-encoded string, and optionally
- * return the decoded result.
- *
- * @param any $value value to check
- * @param and &$decoded decoded result (only valid if method returns true)
- * @return bool
- */
- final public static function is_json($value, &$decoded = NULL)
- {
- if (!is_string($value))
- {
- return false;
- }
-
- return (($decoded = @json_decode($value)) !== NULL) || (json_last_error() === JSON_ERROR_NONE);
- }
-
- }
- /**
- * The SparkInflector class is used to convert strings to/from under_score and CamelCase.
- * This class may be used by core classes, application classes and plugs.
- * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- final class SparkInflector
- {
- /**
- * Convert an indentifier to readable text ("example_identifier" -> "Example Identifier").
- *
- * @param string $s
- * @return string
- */
- final public static function humanize($s)
- {
- return ucfirst(str_replace('_', ' ', $s));
- }
- /**
- * Convert readable text to identifier ("Example Identifier" -> "example_identifier").
- *
- * @param string $s
- * @return string
- */
- final public static function dehumanize($s)
- {
- return strtolower(str_replace(' ', '_', $s));
- }
- /**
- * Convert an indentifier to CamelCase ("example_identifier" -> "ExampleIdentifier").
- *
- * @param string $s
- * @return string
- */
- final public static function camelize($s)
- {
- return str_replace(' ', '', ucwords(str_replace('_', ' ', $s)));
- }
- /**
- * Convert an indentifier from CamelCase to underscores ("ExampleIdentifier" -> "example_identifier").
- *
- * @param string $s
- * @return string
- */
- final public static function decamelize($s)
- {
- return trim(strtolower(preg_replace(array('/([A-Z])([a-z])/', '/([a-z])([A-Z])/'), array('_$1$2', '$1_$2'), $s)), '_');
- }
- }
- /**
- * The SparkConfig class is used to store and retrieve configuration data.
- * This class may be used by core classes, application classes and plugs.
- * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- final class SparkConfig
- {
- private $_config; // key-value dictionary of configuration settings
-
- /**
- * Construct a new Config object.
- *
- * @param array $config: key-value dictionary of configuration values
- */
- public function __construct(&$config)
- {
- $this->_config =& $config;
- }
- /**
- * Retrieve a previously stored configuration item.
- * If the item is not found, return the provided default value (if any).
- *
- * @param string $key Name of configuration parameter to retrieve
- * @param string $default Default value to return if configuration parameter not found
- * @return any Value of configuration parameter if found, or default if not found
- */
- final public function get($key, $default = NULL)
- {
- return self::paramGet($this->_config, $key, $default);
- }
- /**
- * Store a new configuration item.
- *
- * @param string $key Name of configuration parameter to store
- * @param string $val Value to store
- */
- final public function set($key, $val)
- {
- $this->_config[$key] = $val;
- }
- /**
- * Retrieve a value from a parameter array.
- * If the item is not found, return the provided default value (if any).
- *
- * @param string $key Name of parameter to retrieve
- * @param string $default Default value to return if parameter not found
- * @return any Value of parameter if found, or default if not found
- */
- final public static function paramGet($arr, $key, $default = NULL)
- {
- return isset($arr[$key]) ? $arr[$key] : $default;
- }
- /**
- * Retrieve a (normalized) PHP configuration setting.
- *
- * @param string $key Name of configuration parameter to retrieve
- * @param string $default Default value to return if configuration parameter not found
- * @return string Value of configuration parameter if found, or default if not found
- */
- final public static function php_ini_get($key, $default = NULL)
- {
- switch (strtolower($val = ini_get($key)))
- {
- case '':
- return $default;
-
- case '0':
- case 'off':
- case 'false':
- case 'no':
- case 'none':
- return false;
-
- case '1':
- case 'on':
- case 'true':
- case 'yes':
- return true;
-
- default:
- return $val;
- }
- }
- }
- /**
- * The SparkObserver class is used to register observable events and their observers.
- * Once events and observers have been registered, events may be fired and all observers
- * registered for those events will be notified.
- * This class may be used by core classes, application classes and plugs.
- * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- final class SparkObserver
- {
- private $_events; // list of observable events
-
- /**
- * Construct a new SparkObserver object.
- *
- */
- public function __construct()
- {
- $this->_events = array();
- }
- /**
- * Register an observer for one or more events.
- *
- * An observer can be one of:
- *
- * a function name
- * a lambda function
- * an array: (class name, static method name)
- * an array: (object, method name)
- *
- * One or more events can be registered for each observer.
- * Events can be specified as either an array of event names or as a
- * comma-delimited string containing one or more event names.
- *
- * @param string $observer Called when registered events fires
- * @param string $events Event (or events) to observe
- */
- final public function observe($observer, $events)
- {
- if (!is_array($events))
- {
- $events = explode(',', str_replace(' ', '', $events));
- }
-
- // add the observer to the event list for each event it is observing
-
- foreach($events as $event)
- {
- $this->_events[$event][] = $observer;
- }
- }
- /**
- * Notify observers that an event has fired.
- * Observers will be passed the event name plus any additional args passed to
- * this method.
- *
- * @param mixed $events Event (or events) to fire
- * @param mixed $params1...$paramN variable argument list to pass to observer
- */
- final public function notify($events)
- {
- if (!is_array($events))
- {
- $events = explode(',', str_replace(' ', '', $events));
- }
-
- // A "composite event" is of the form "general:more_specific:most_specific",
- // a series of component events separated by colons in which each subsequent
- // component is more specific than the preceding component. For composite
- // events, we send multiple notifications, so listeners may listen on on as
- // specific a component as desired. For example, given composite event:
- // "site_changed:content:page_saved:edited"
- // we would send the event notification to listeners of the folowing four events:
- // "site_changed"
- // "site_changed:content"
- // "site_changed:content:page_saved"
- // "site_changed:content:page_saved:edited"
- //
- // Note that the specific event (in this case, "site_changed:content:page_saved:edited")
- // is always passed as the first argument to the listener's callback function.
-
- foreach ($events as $event)
- {
- $first = true;
- foreach (explode(':', $event) as $component)
- {
- if (!empty($component))
- {
- if ($first)
- {
- $notification = $component;
- $first = false;
- }
- else
- {
- $notification .= ':' . $component;
- }
- if (isset($this->_events[$notification]))
- {
- foreach ($this->_events[$notification] as $observer)
- {
- $args = func_get_args();
- $args[0] = $event;
- call_user_func_array($observer, $args);
- }
- }
- }
- }
- }
- }
- }
- /**
- * The Spark class is Spark/Plug's object factory. It loads plugs and constructs
- * object hierarchies on the fly when instantiating a new object.
- * This class may be used by core classes, application classes and plugs.
- * This is one of the few *final* core classes in Spark/Plug (not extensible by plugs).
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- final class Spark
- {
- private $_spDir; // Spark/Plug directory
- private $_observer; // global observer
- private $_plugList; // list if installed plugs indexed by class name
- private $_plugMap; // maps class names to list of plugs that implement/extend that class name
- private $_plugCacheDir; // directory containing cached plugs
- private $_plugDirStack; // directories containing plugs
- // --------------------------------------------------------------------------
- /**
- * Construct a new Spark object, bootstrap the application, and "activate" plugs.
- * Only plugs that extend (derive directly from) the SparkPlug class need be specified
- * at this time. Additional plugs may be activated at any time by invoking the findPlugs()
- * method of this class.
- *
- * @param array $classes Array of names of plugs to "activate" (make available to the running application)
- * @param string|array $searchPaths Optional path(s) to search for plugs
- * @param string $cacheDir Optional path to plug cache directory
- */
- final public function __construct($classes = array(), $searchPaths = NULL, $cacheDir = NULL)
- {
- $this->_observer = new SparkObserver();
- $this->_plugList = array();
- $this->_plugMap = array();
- $this->_plugCacheDir = NULL;
- $this->_spDir = dirname(__FILE__);
- if (!empty($cacheDir))
- {
- $this->setPlugCacheDir($cacheDir);
- $this->_observer->observe(array($this, 'flushPlugCache'), 'Spark:cache:request_flush');
- }
-
- if (empty($searchPaths))
- {
- $searchPaths = array(NULL);
- }
- foreach ($searchPaths as $dir)
- {
- if (empty($dir))
- {
- $this->_plugDirStack[] = "{$this->_spDir}/plugs";
- }
- else
- {
- if ($dir[0] !== '/')
- {
- $dir = "{$this->_spDir}/{$dir}";
- }
- $this->_plugDirStack[] = $dir;
- }
- }
- $this->findPlugs($classes);
- // load the SparkPlug class and all its plugs
- $this->loadClass('SparkPlug');
- }
- // --------------------------------------------------------------------------
- /**
- * Return the global observer object
- *
- * @return object Observer object
- */
- final public function observer()
- {
- return $this->_observer;
- }
- // --------------------------------------------------------------------------
- /**
- * Initialize the Spark object and load plugs that extend core classes.
- * This method is invoked at the bottom of this file.
- *
- */
- final public function init()
- {
- // load all the core classes and their respective plugs
-
- $this->loadClass('SparkModel');
- $this->loadClass('SparkView');
- $this->loadClass('SparkController');
- $this->loadClass('SparkApplication');
- }
- // --------------------------------------------------------------------------
- /**
- * Returns the path to the plug cache directory.
- *
- * @return string Path to plug cache directory
- */
- // --------------------------------------------------------------------------
- public function getPlugCacheDir()
- {
- return $this->_plugCacheDir;
- }
- // --------------------------------------------------------------------------
- /**
- * Call this method to change the plug cache directory for subsequent plug cache operations.
- * Used to dynamically change where code is cached based on caller's criteria (eg. host name).
- * Should be called as early as possible. Note that some classes may have already been cached
- * by the time this method is called.
- *
- * @param string $cacheDir Path to new plug cache directory
- *
- * @return string Previous cache directory path
- */
- // --------------------------------------------------------------------------
- public function setPlugCacheDir($cacheDir)
- {
- $oldDir = $this->_plugCacheDir;
- $this->_plugCacheDir = $cacheDir;
- if ($this->_plugCacheDir[0] !== '/')
- {
- $this->_plugCacheDir = "{$this->_spDir}/{$this->_plugCacheDir}";
- }
- return $oldDir;
- }
- // --------------------------------------------------------------------------
- /**
- * Call this method to flush the plug code cache.
- * Not usually called directly, as it can be invoked via the "Spark:cache:request_flush"
- * notification.
- */
- // --------------------------------------------------------------------------
- public function flushPlugCache()
- {
- if (!empty($this->_plugCacheDir))
- {
- $iter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_plugCacheDir), RecursiveIteratorIterator::CHILD_FIRST);
- foreach ($iter as $file)
- {
- $filePath = $file->getPathname();
- if ($file->isDir())
- {
- $fileName = $file->getFilename();
- if ($fileName !== '.' && $fileName !== '..')
- {
- rmdir($filePath);
- }
- }
- else
- {
- unlink($filePath);
- }
- }
- $this->_observer->notify('Spark:cache:flush');
- }
- }
- // --------------------------------------------------------------------------
- /**
- * Call this method to make additional plugs available to be loaded.
- * Subsequent invocations of manufacture() will consider these additional plugs when
- * determining which plugs to load. If $searchPaths parameter is provided, it will
- * be used instead of the main plug directory for the scope of this invocation only.
- * If $callback is provided, it will be called for each plug and passed the plug as a
- * parameter.
- *
- * @param array $classes Array of plug names to make available
- * @param string|array $searchPaths Optional path(s) to search for plugs
- * @param string $callback Callback (optional)
- */
- final public function findPlugs($classes, $searchPaths = NULL, $callback = NULL)
- {
- if (!empty($classes))
- {
- if (empty($searchPaths))
- {
- $searchPaths = $this->_plugDirStack;
- }
- elseif (!is_array($searchPaths))
- {
- $searchPaths = array($searchPaths);
- }
-
- foreach ($classes as $class)
- {
- // allow nested plug directories
-
- if ($subPath = (strpos($class, '/') === false) ? '' : '/'.dirName($class))
- {
- $class = basename($class);
- }
- foreach ($searchPaths as $plugDir)
- {
- $dirName = $plugDir . $subPath . '/' . SparkInflector::decamelize($class);
- if (file_exists($dirName))
- {
- break;
- }
- }
- // load plug's manifest
- unset($plug);
- require($dirName . '/manifest.php');
-
- // add plug to plug map
- if (isset($plug))
- {
- $plugs = $plug['plugs'];
- unset($plug['plugs']);
-
- foreach ($plugs as $nextPlug)
- {
- if ($callback)
- {
- $nextPlug = array_merge($plug, $nextPlug); // make sure callback has all the plug info
- // Note: We do not use call_user_func() here because it does not allow pass-by-reference
- if (is_string($callback))
- {
- if (!$callback($nextPlug))
- {
- continue;
- }
- }
- elseif (is_array($callback))
- {
- if (!$callback[0]->$callback[1]($nextPlug))
- {
- continue;
- }
- }
- }
- $name = $nextPlug['name'];
-
- // if an explicit path is provided, use it, otherwise deduce a path from the plug's name
-
- if ($path = @$nextPlug['path'])
- {
- // explicit path can be absolute, relative to plugs directory, or relative to plug's own directory
-
- if ($path[0] != '/')
- {
- if (!strncasecmp($path, 'plugs/', 6))
- {
- $path = $plugDir . '/' . substr($path, 6);
- }
- else
- {
- $path = $dirName . '/' . $path;
- }
- }
- }
- else
- {
- $path = $dirName . '/' . SparkInflector::decamelize($name) . '.php';
- }
-
- // plug can be one of two types:
- //
- // 1. an extension plug that augments an existing class,
- // or
- // 2. a base plug that simply provides some new service
-
- if ($extends = @$nextPlug['extends'])
- {
- // extension plugs can declare a preferred load order to help avoid conflicts
-
- $order = isset($nextPlug['order']) ? max(1, min(100, $nextPlug['order'])) : 50;
- }
- else
- {
- $extends = $name;
- $order = 0;
- }
- $plugInfo = array('name'=>$name, 'extends'=>$extends, 'order'=>$order, 'file'=>$path);
- if (!empty($nextPlug['requires']))
- {
- if (is_string($requires = $nextPlug['requires']))
- {
- $requires = array_map('trim', explode(',', $requires));
- }
- $plugInfo['requires'] = $requires;
- }
- if (($order === 0) && isset($nextPlug['base_class']))
- {
- $plugInfo['base_class'] = $nextPlug['base_class'];
- }
- $this->_plugList[$name] =& $plugInfo;
- $this->_plugMap[$extends][$order][] =& $plugInfo;
- unset($plugInfo);
- }
- unset($plug);
- }
- }
-
- // sort plugs according to load order
-
- foreach(array_keys($this->_plugMap) as $key)
- {
- ksort($this->_plugMap[$key]);
- }
- }
- }
- // --------------------------------------------------------------------------
- /**
- * Call this method to make an additional plug available to be loaded.
- * Subsequent invocations of manufacture() will consider this plug when
- * determining which plugs to load. Unlike findPlugs(), this method can
- * accept a callback to load the plug's code, rather than a file.
- *
- * @param array $plug:
- * (
- * string 'name' => name of plug to add,
- * string 'requires' => name(s) of class(es) (if any) required by this plug,
- * string 'extends' => name of class (if any) being extended by this plug,
- * int 'order' => load order for this plug
- * string 'file' => location of plug's code file, OR
- * mixed 'callback' => callback to create the plug,
- */
- final public function addPlug($plug)
- {
- if (!empty($plug['requires']))
- {
- if (is_string($requires = $plug['requires']))
- {
- $requires = array_map('trim', explode(',', $requires));
- }
- $plug['requires'] = $requires;
- }
- $plug['order'] = !empty($plug['extends']) ? max(1, min(100, !empty($plug['order']) ? $plug['order'] : 100)) : 0;
- if (empty($plug['extends']))
- {
- $plug['extends'] = $plug['name'];
- }
- $this->_plugList[$plug['name']] =& $plug;
- $this->_plugMap[$plug['extends']][$plug['order']][] =& $plug;
- ksort($this->_plugMap[$plug['extends']]);
- }
-
- // --------------------------------------------------------------------------
- /**
- * Call this method to retrieve information about an installed plug.
- *
- * @param string $name: => name of plug
- * @return array of plug info
- */
- final public function getPlug($name)
- {
- return @$this->_plugList[$name];
- }
-
- // --------------------------------------------------------------------------
- /**
- * Call this method to retrieve information about all installed plugs that extend a specified plug.
- *
- * @param string $name: => name of extended plug
- * @return array of plug infos
- */
- final public function getExtensions($name)
- {
- if (empty($this->_plugMap[$name]))
- {
- return NULL;
- }
-
- $extensions = array();
- foreach ($this->_plugMap[$name] as $plugList)
- {
- $extensions = array_merge($extensions, $plugList);
- }
-
- return $extensions;
- }
-
- // --------------------------------------------------------------------------
- /**
- * Dynamically build a class hierarchy and instantiate a new object.
- * This method is used to create any object that you want to be extensible.
- *
- * For example, any object derived from SparkPlug may call:
- *
- * $myObject = $this->factory->manufacture('MyClass');
- *
- * and $myObject will be created and will include the functionality provided by
- * all plugs that extend MyClass.
- *
- * However, if instead you call:
- *
- * $myObject = new MyClass;
- *
- * you will still create a MyClass object, but it will not include any plug functionality.
- *
- * @param string $class Name of class to instantiate
- * @return object
- */
- final public function manufacture($class)
- {
- $this->_observer->notify('Spark:manufacture:' . $class, $class);
- // load the class and all its extension plugs
-
- $class = $this->loadClass($class);
- // get parameters for constructor (remove the class name from start of args)
- $params = func_get_args();
- array_shift($params);
-
- // return call_user_func_array(array($class, '__construct'), $params);
- // can't call a constructor via call_user_func_array() because it is not a static method
- switch ($c = count($params))
- {
- case 0:
- return new $class();
- case 1:
- return new $class($params[0]);
- case 2:
- return new $class($params[0], $params[1]);
- case 3:
- return new $class($params[0], $params[1], $params[2]);
- case 4:
- return new $class($params[0], $params[1], $params[2], $params[3]);
- case 5:
- return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
- case 6:
- return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5]);
- case 7:
- return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6]);
- case 8:
- return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7]);
- case 9:
- return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7], $params[8]);
- case 10:
- return new $class($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7], $params[8], $params[9]);
- default:
- throw new SparkException('too many arguments (' . $c . ') passed to constructor (max 10)');
- }
- }
- // --------------------------------------------------------------------------
- /**
- * Call this method to load a class and all its extension plugs.
- * Optionally cache the class to the plug cache directory.
- *
- * @param string $class Name of class to load
- * @param boolean $abstract True if this is an abstract base class
- * @return string Name of instantiated class (alternate implementation may change class name)
- */
- final public function loadClass($class, $abstract = false)
- {
- // no need to load a class more than once
- if (class_exists($class, false))
- {
- return $class;
- }
-
- // If caching is enabled, check the cache directory for this class.
- if (!empty($this->_plugCacheDir))
- {
- $cacheFile = $this->_plugCacheDir . '/' . $class . '.php';
- if ((@include $cacheFile) !== false)
- {
- return $class;
- }
- }
-
- // Extensible classes are always declared with an underscore prefix on the name,
- // which allows us to use the class as a base class for plugs, while leaving the
- // ultimate class name (without the underscore) free for dynamic creation once
- // all the plugs have been loaded.
-
- $baseClass = '_' . $class;
- // if the base class does not exist, it is a plug itself and needs to be loaded
-
- if (isset($this->_plugMap[$class])) // any plugs want to extend this class?
- {
- foreach ($this->_plugMap[$class] as $plugs)
- {
- foreach ($plugs as $plug)
- {
- $plugName = $plug['name']; // plug's name
- $plugOrder = $plug['order']; // plug's load order
- $plugCode = false; // plug's code
-
- // take care of dependencies
-
- foreach ((array)@$plug['requires'] as $dependency)
- {
- $this->loadClass($dependency);
- }
- // To allow easier integration with external class libraries, the manifest may
- // specify alternate class names for base classes.
-
- if (($plugOrder == 0) && isset($plug['base_class']))
- {
- $baseClass = $plug['base_class'];
- }
-
- // load the plug's code
-
- if ($plugFile = @$plug['file'])
- {
- $plugCode = file_get_contents($plugFile);
- }
- elseif ($callback = @$plug['callback'])
- {
- // Note: We do not use call_user_func() here because it does not allow pass-by-reference
-
- if (is_string($callback))
- {
- $plugCode = $callback($plug);
- }
- elseif (is_array($callback))
- {
- $plugCode = $callback[0]->$callback[1]($plug);
- }
- }
-
- if ($plugCode !== false)
- {
- $plugCode = preg_replace("/\s*<\?(php)?/", '', $plugCode, 1);
- // Here's the tricky bit... We dynamically rewrite the plug's class definition to build our
- // class hierarchy. (Self-modifying code - oh my!)
- // So, each plug is loaded in turn and serves as the base class for the next plug to be loaded.
- // Performance of this mechanism actually turns out to be quite respectable. The major downside
- // is that opcode cachers will not be able to cache the bytecode of plugs loaded in this manner,
- // (which is generally true of eval'd code). However, this drawback may be mitigated by enabling
- // plug caching, which writes the entire class inheritance chain to a single class file.
-
- if ($plugOrder != 0) // not a base class?
- {
- $firstClass = isset($plug['first_class']) ? $plug['first_class'] : $plugName;
- $plugCode = preg_replace("/class\s+{$firstClass}\s+extends\s+(?:{$class}|{$baseClass})/", "class {$firstClass} extends {$baseClass}", $plugCode, 1, $count);
-
- if ($count !== 1) // something fishy here, best abort
- {
- throw new SparkException('plug err: Could not load plug "' . ($plugFile ? $plugFile : $plugName) . '"');
- }
- }
-
- // create cache file if caching enabled
-
- if (!isset($cacheFileHandle) && !empty($this->_plugCacheDir))
- {
- if (($cacheFileHandle = @fopen($cacheFile, 'w')) !== false)
- {
- if (fwrite($cacheFileHandle, "<?php\n") === false)
- {
- throw new SparkException('plug err: Could not write plug cache file "' . $cacheFile . '"');
- }
- }
- }
-
- if (!empty($cacheFileHandle))
- {
- if (fwrite($cacheFileHandle, $plugCode) === false)
- {
- throw new SparkException('plug err: Could not write plug cache file "' . $cacheFile . '"');
- }
- }
- elseif ($this->loadPlug($plugCode) === false)
- {
- throw new SparkException('plug err: Could not load plug "' . ($plugFile ? $plugFile : $plugName) . '"');
- }
-
- // plug successfully loaded and will serve as the base class for the next to load (if any)
-
- if ($plugOrder != 0) // not a base class?
- {
- $baseClass = $plugName;
- }
- }
- }
- }
- }
-
- // Finally, dynamically generate the requested class as a simple wrapper class.
- // This makes the class name a legitimate object in the PHP namespace so it can be referred to by
- // other functions, etc.
-
- $plugCode = "\nif (!class_exists('{$class}', false)) { " . ($abstract ? 'abstract ' : '') . "class {$class} extends {$baseClass} {} }\n";
- if (!empty($cacheFileHandle))
- {
- if (fwrite($cacheFileHandle, $plugCode) === false)
- {
- throw new SparkException('plug err: Could not write plug cache file "' . $cacheFile . '"');
- }
- fclose($cacheFileHandle);
- include $cacheFile;
- }
- else
- {
- if (!class_exists($baseClass, false))
- {
- throw new SparkException('loader err: Could not load class "' . $baseClass . '"');
- }
- $this->loadPlug($plugCode);
- }
- return $class;
- }
-
- // --------------------------------------------------------------------------
- /**
- * Private method to isolate the loading of the plug's code.
- *
- * @param string $plugCode Plug code to load into PHP interpreter
- * @return mixed Result of loading the plug's code
- */
- final private function loadPlug($plugCode)
- {
- return eval($plugCode);
- }
- }
- /**
- * The _SparkPlug (SparkPlug) class is Spark/Plug's universal base object class. Any
- * class that wants access to the application object and/or the Spark factory object
- * should derive from this class or one of its descendants. This class is not meant to
- * be instantiated directly. When specifying this class as a base class in your class
- * definition, be sure to extend SparkPlug (not _SparkPlug). Otherwise, your derived class
- * will not inherit the capabilities of any plugs that extend this class.
- * This class may be used by core classes, application classes and plugs.
- *
- * @package Spark/Plug
- */
- // -----------------------------------------------------------------------------
- abstract class _SparkPlug
- {
- public $app; // convenience reference to global application object
- public $config; // convenience reference to application configuration store
- public $factory; // convenience reference back global object factory
- public $observer; // convenience reference to global observer
- // --------------------------------------------------------------------------
- /**
- * Construct a new SparkPlug object. Make key application objects available to all
- * derived classes.
- */
- public function __construct()
- {
- $this->app = SparkApplication::instance();
- $this->config = $this->app->config();
- $this->factory = $this->app->factory();
- $this->observer = $this->factory->observer();
- }
- /**
- * Create a new model object. Calls through to the application's newModel() method.
- *
- * @param string $name Name of model class to instatiate
- * @return object Model object
- */
- public function newModel($name, $params = NULL)
- {
- return $this->app->newModel($name, $params);
- }
- /**
- * Create a full url to the specified (static) path. Calls through to the application's urlToStatic() method.
- *
- * @param string $path Path to which to create full URL
- * @param bool $withHost Whether to include host in URL
- * @param bool $withScheme Whether to include scheme in URL
- * @param bool $secure Whether to generate a secure URL (true), a non-secure URL (false), or default (NULL)
- * @return string URL
- */
- public function urlToStatic($path, $withHost = false, $withScheme = true, $secure = NULL)
- {
- return $this->app->urlToStatic($path, $withHost, $withScheme, $secure);
- }
- /**
- * Create a full url to the specified (dynamic) path. Calls through to the application's urlTo() method.
- *
- * @param string $path Path to which to create full URL
- * @param bool $withHost Whether to include host in URL
- * @param bool $withScheme Whether to include scheme in URL
- * @param bool $secure Whether to generate a secure URL (true), a non-secure URL (false), or default (NULL)
- * @return string URL
- */
- public function urlTo($path, $withHost = false, $withScheme = true, $secure = NULL)
- {
- return $this->app->urlTo($path, $withHost, $withScheme, $secure);
- }
- /**
- * Create a full url to the specified (static) secure path. Calls through to the application's urlToStaticSecure() method.
- *
- * @param string $path Path to which to create secure URL
- * @return string URL
- */
- public function urlToStaticSecure($path)
- {
- return $this->app->urlToStaticSecure($path);
- }
- /**
- * Create a full url to the specified (dynamic) secure path. Calls through to the application's urlToSecure() method.
- *
- * @param string $path Path to which to create secure URL
- * @return string URL
- */
- public function urlToSecure($path)
- {
- return $this->app->urlToSecure($path);
- }
- /**
- * Create a redirect to the specified (static) path. Calls through to the application's urlToStatic() method.
- *
- * @param string $path Path to which to create full URL to redirect to
- * @param bool $secure Whether…
Large files files are truncated, but you can click here to view the full file