/fastpage/lib/fastpage.php
PHP | 3412 lines | 1755 code | 443 blank | 1214 comment | 435 complexity | 3843cee3f4221c92b69b0ff3ddc27fc0 MD5 | raw file
Possible License(s): LGPL-2.1
- <?php
- /**
- * FastPage
- *
- * Licensed under the MIT License
- *
- * @copyright Copyright 2011, ideaman's Inc. (http://www.ideamans.com)
- * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
- */
- /**
- * Version.
- */
- define( 'FASTPAGE_VERSION', '1.0.0 RC4' );
- /**
- * Error constant for profiling.
- */
- define( 'E_FP_PROFILE', 0x00 );
- /**
- * A class gathering utility static functions.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga@ideaman's Inc.
- */
- class FastPage_Util {
- /**
- * Time units.
- */
- const minute = 60;
- const hour = 3600;
- const day = 86400;
- const week = 604800;
- const month = 2592000;
- const year = 31536000;
- /**
- * Aliases of server variables.
- */
- public static $server_aliases = array(
- 'HTTP_HOST' => array( 'HTTP_HOST', 'SERVER_NAME' ),
- 'SCRIPT_NAME' => array( 'SCRIPT_NAME', 'PHP_SELF' ),
- );
- /**
- * Returns millisecond time.
- *
- * @return float Current epoc time in millisecond.
- */
- public static function millisec() {
- return function_exists('microtime')? microtime(true) * 1000.0: time() / 1000.0;
- }
- /**
- * Returns memory_usege safety.
- *
- * @return integer Memory usage.
- */
- public static function memory_usage() {
- return function_exists('memory_get_usage')? memory_get_usage(): 0;
- }
- /**
- * Splits path like url or directory, and resolves periods.
- *
- * @param string $path A target path as string.
- * @param string $separator Separator of target such as / or DIRECTORY_SEPARATOR. The default is '/'.
- * @return array Partials of the path as an array.
- */
- public static function split_path( $path, $separator = '/' ) {
- // Split path with separator.
- if ( strlen($separator) == 1 ) {
- $paths = explode( $separator, $path );
- } else {
- $regex = '![' . preg_quote($separator) . ']!';
- $paths = preg_split( $regex, $path );
- }
- $result = array( array_shift($paths) );
- // Join path partials.
- $count = count($paths);
- for ( $i = 0; $i < $count; $i++ ) {
- $partial = $paths[$i];
- if ( $partial == '..' && $i != 0 ) {
- // Back to the parent.
- array_pop( $result );
- } else if ( !$partial && $i > 0 && $i < $count - 1 ) {
- // Skip a blank not the first or the last.
- } else if ( $partial == '.' ) {
- // Skip as same directory.
- } else {
- // Push to the last.
- $result []= $partial;
- }
- }
- return $result;
- }
- /**
- * Catenate some path partials and normalize it.
- *
- * @param variable $PARTIALS Partials of path as variable numbers arguments.
- * @return string Catenated path.
- */
- public static function cat_path( ) {
- if ( func_num_args() < 1 ) return '';
- // Expand args.
- $args = func_get_args();
- $paths = array();
- foreach ( $args as $arg ) {
- if ( is_array($arg) ) {
- $paths = array_merge( $paths, $arg );
- } else if ( is_string($arg) ) {
- $paths []= $arg;
- }
- }
- $path = join( DIRECTORY_SEPARATOR, $paths );
- // Path maybe contains mixed separators in windows.
- $separator = DIRECTORY_SEPARATOR;
- if ( $separator != '/' ) $separator .= '/';
- $partials = self::split_path( $path, $separator );
- return join( DIRECTORY_SEPARATOR, $partials );
- }
- /**
- * Ensure include_path.
- *
- * @param string $path Path to set include path.
- */
- public static function ensure_include_path( $path ) {
- // Check if include_path has the path.
- $include_path = ini_get('include_path');
- $include_paths = explode( PATH_SEPARATOR, $include_path );
- // Append if the path does not exists.
- if ( false === array_search( $path, $include_paths ) ) {
- $include_path .= PATH_SEPARATOR . $path;
- ini_set( 'include_path', $include_path );
- }
- }
- /**
- * Enclose string with quotes.
- *
- * @param string $value A string to quote.
- * @param string $quotation Optional. Character of quotation. Default is double quotation.
- */
- public static function quote( $value, $quotation = '"' ) {
- if ( !isset($value) ) $value = '';
- return $quotation . $value . $quotation;
- }
- /**
- * Gets the Etag(entity tag) of a file formated like Apache.
- *
- * @param string $path A path of file to get Etag.
- * @param boolean $quote If true, double-quote the result.
- * @return string The Etag of the file.
- */
- public static function file_etag( $path, $quote = true ) {
- if ( file_exists($path) && false !== ( $stats = stat($path) ) ) {
- $etag = sprintf('%x-%x-%x', $stats['ino'], $stats['size'], $stats['mtime'] * 1000000 );
- if ( $quote ) $etag = self::quote( $etag );
- return $etag;
- }
- return null;
- }
- /**
- * Strips single and double quotes at the head and at the last.
- *
- * @param string $value A string to remove quotation.
- * @return string The string removed quotation.
- */
- public static function strip_quotes( $value ) {
- // FIXME: Shoud return blank string ''?
- if ( !$value ) return $value;
- // Remove the first quote.
- $first = substr( $value, 0, 1 );
- if ( $first == '"' || $first == "'" ) {
- $value = substr( $value, 1 );
- } else {
- $first == '';
- }
- // Remove the last quote.
- $last = substr( $value, -1, 1 );
- if ( $last == $first || ( !$first && ( $last == '"' || $last == "'" ) ) ) {
- $value = substr( $value, 0, -1 );
- }
- return $value;
- }
- /**
- * Strip URL parameters.
- *
- * @param string $url A URL to remove parameters.
- * @return string The URL removed parameters.
- */
- public static function strip_url_params( $url ) {
- if ( !$url ) return $url;
- $url = preg_replace( '![\?#].*!', '', $url );
- return $url;
- }
- /**
- * Get server variable by a key or some keys.
- *
- * @param mixed $name A key string or an array of keys.
- * @param mixed $default The default value if server variable not found.
- * @return mixed The server value. Return null if not found.
- */
- public static function server( $name, $default = null ) {
- // Search alias.
- if ( is_string($name) && isset(self::$server_aliases[$name]) ) {
- $name = self::$server_aliases[$name];
- }
- if ( is_array($name) ) {
- foreach ( $name as $var ) {
- if ( isset($_SERVER[$var]) && $_SERVER[$var] ) {
- return $_SERVER[$var];
- }
- }
- } else if ( isset($_SERVER[$name]) && $_SERVER[$name] ) {
- return $_SERVER[$name];
- }
- return $default;
- }
- /**
- * Match IP address with patterns.
- *
- * @param mixed $patterns IP address patterns as a string or an array.
- * @param string $ip IP address to match.
- * @param boolean $include_self Add IP address of myself.
- * @return boolean Matched or not.
- */
- public static function match_ips( $patterns, $ip, $include_self = true ) {
- // Normalize patterns to an array.
- if ( !is_array($patterns) ) {
- $patterns = preg_split( '!\\s*,\\s*!', $patterns );
- }
- // Add myself to patterns.
- if ( $include_self ) {
- $patterns []= '127.0.0.1';
- if ( $server_addr = self::server('SERVER_ADDR') ) {
- $patterns []= $server_addr;
- }
- }
- // Check each pattern.
- foreach ( $patterns as $p ) {
- // Prefix matching.
- if ( !$p ) continue;
- if ( $p == substr( $ip, 0, strlen($p) ) ) {
- return true;
- }
- }
- return false;
- }
- /**
- * Unserialize as object of a class.
- *
- * @param string $serialized Serialized string.
- * @param string $class Class required.
- * @return mixed The unserialized value.
- */
- public static function unserialize_as( &$value, $class = null ) {
- if ( !$value ) return null;
- $result = @unserialize($value);
- if ( $class ) {
- if ( !is_a( $result, $class ) ) {
- return null;
- }
- }
- return $result;
- }
- }
- /**
- * Exception class.
- *
- * Currently almost empty and not to be used.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga
- */
- class FastPage_Exception extends Exception { }
- /**
- * Base object class supports basic accessors.
- *
- * Properties stored in $_vars member.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga@ideaman's Inc.
- */
- class FastPage_Object {
- /**
- * Members store.
- */
- protected $_vars = array();
- /**
- * Constructor.
- *
- * @param array $defaults Default values of object.
- */
- public function __construct( $defaults = null ) {
- if ( isset($defaults) && is_array($defaults) ) {
- $this->_vars = $defaults;
- }
- }
- /**
- * Property getter.
- *
- * Implimentatin of getter. Get named property from $_vars member.
- *
- * @param string $name Name of the property.
- * @return mixed The property value. If not defined, return null.
- */
- public function __get( $name ) {
- if ( isset($this->_vars[$name]) ) {
- return $this->_vars[$name];
- }
- return null;
- }
- /**
- * Property setter.
- *
- * Implimentation of setter. Set named property to $_vars member. Not to be used directory.
- *
- * @param string $name Name of the property.
- * @param mixed $value Value of the property.
- * @return mixed The value echoed back.
- */
- public function __set( $name, $value ) {
- $this->_vars[$name] = $value;
- return $value;
- }
- /**
- * Dictionary accessor.
- *
- * Implimentation of dictionary type accessor.
- * Get and set dictionary type named property.
- * Usually to be wrapped as other function.
- *
- * @param string $namespace Name of the dictionary.
- * @param string $name Key of the dictionary value.
- * @param mixed $value The dictionary value to set. If you set null, this function behaves as a getter. Default is null.
- * @return mixed The dictionary value. No match found, return null.
- */
- public function __dictionary( $namespace, $name, $value = null ) {
- if ( isset($value) ) {
- // As a setter.
- $this->_vars[$namespace][$name] = $value;
- return $value;
- }
- // As a getter.
- if ( isset($this->_vars[$namespace][$name]) ) {
- return $this->_vars[$namespace][$name];
- }
- return null;
- }
- /**
- * Name keys of all properties.
- *
- * @return Array Names of all properties as an array.
- */
- public function keys() {
- return array_keys( $this->_vars );
- }
- /**
- * Get all properties as an array.
- *
- * @return Array Properties of the object.
- */
- public function to_array() {
- return $_vars;
- }
- /**
- * Sets values from an array or another object.
- *
- * @param mixed $values An array of properties or an another obejct.
- * @param boolean $override To be overridden each properties already exists. Default is true.
- */
- public function merge_values( $values, $override = true ) {
- if ( is_array($values) ) {
- foreach ( $values as $name => $value ) {
- if ( $override || !isset($this->_vars[$name]) ) {
- $this->_vars[$name] = $value;
- }
- }
- } else if ( is_object($values) && is_a( $values, 'FastPage_Object' ) ) {
- $this->merge_values( $values->to_array(), $override );
- }
- }
- }
- /**
- * Log record.
- *
- * Currently, a simple class of basic object.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var integer $timestamp Timestamp in milli second.
- * @var integer $memory_usage Memory usage.
- * @var integer $level Lovel of log in PHP constant like E_ERROR.
- * @var integer $message The log message human readable.
- */
- class FastPage_LogRecord extends FastPage_Object { }
- /**
- * Profile record.
- *
- * Current, a simple class of basic object.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var string $name Process name.
- * @var integer $start Timestamp started in milli second.
- * @var integer $end Timestamp ended in milli second.
- * @var integer $duration Duration in milli second.
- */
- class FastPage_Profile extends FastPage_Object { }
- /**
- * Debugger.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var boolean $enabled Enabled debugging.
- * @var array $logs Log records.
- * @var integer $error_message_type Message type to call error_log on error record added.
- * @var string $error_destination Destination to call error_log on error record added.
- * @var float $start Starting timestamp.
- * @var float $last Last logging timestamp.
- * @var FastPage_LogRecord $last Last log record.
- * @var float $debugging_time Total debugging time.
- * @var integer $debbuggin_memory_usage Total debugging memory usage.
- * @var integer $debugging_in_nest Nesting depth of debugging time.
- */
- class FastPage_Debug extends FastPage_Object {
- public $logs = array();
- public $start = 0.0;
- public $last = 0.0;
- public $debugging_time = 0.0;
- public $debugging_memory_usage = 0;
- public $debugging_in_nest = 0;
- /**
- * Singleton instance.
- */
- public static $instance;
- /**
- * Numeric log members.
- *
- * These members will be computed delta and sum.
- */
- public static $numeric_log_members = array( 'timestamp', 'memory_usage', 'memory_peak_usage' );
- /**
- * Get singleton instance.
- *
- * @return FastPage_Debug The instance.
- */
- public static function instance() {
- return self::$instance;
- }
- /**
- * Start debug.
- *
- * @param integer $message_type Optional. Passed to error_log function.
- * @param integer $destination Optional. Passed to error_log function.
- * @return boolean Current debug mode.
- */
- public static function start( $message_type = 0, $destination = '' ) {
- // Starting time. If set the time, set global $FASTPAGE_START;
- $time = FastPage_Util::millisec();
- global $FASTPAGE_START;
- if ( $FASTPAGE_START ) $time = $FASTPAGE_START;
- $debug = new self;
- $in = $debug->debug_in();
- $debug->start = $debug->last = $time;
- $debug->error_message_type = $message_type;
- $debug->error_destination = $destination;
- // Enabled debug.
- $debug->enabled = true;
- // Singleton.
- self::$instance = $debug;
- $debug->debug_out($in);
- return $debug;
- }
- /**
- * Start to debugging time.
- *
- * @return array Current time in millisecond and memory usage.
- */
- public function debug_in() {
- if ( !$this->enabled ) return null;
- $this->debugging_in_nest++;
- return array( FastPage_Util::millisec(), FastPage_Util::memory_usage() );
- }
- /**
- * Stop to debugging time.
- *
- * @param array $in Time and memory usage to debug in.
- */
- public function debug_out( $in ) {
- if ( !is_array($in) ) return;
- $this->debugging_in_nest--;
- if ( $this->debugging_in_nest == 0 ) {
- $out = FastPage_Util::millisec();
- $this->debugging_time += ( $out - $in[0] );
- $out = FastPage_Util::memory_usage();
- $this->debugging_memory_usage += ( $out - $in[1] );
- }
- }
- /**
- * Add new log record.
- *
- * @param FastPage $fastpage FastPage instance.
- * @param integer $level Log level in PHP constants.
- * @param string $message Log message human readable.
- * @param mixed $hint Hint value for the log record.
- */
- public function add_log( $fastpage, $level, $message, $hint = null ) {
- // Not in debugging, do nothing.
- if ( !$this->enabled ) return;
- $in = $this->debug_in();
- // Timestamp and usage.
- $msec = FastPage_Util::millisec();
- $usage = FastPage_Util::memory_usage();
- $peak_usage = function_exists('memory_get_peak_usage')? memory_get_peak_usage(true): $usage;
- // Log record.
- $record = new FastPage_LogRecord;
- $record->timestamp = $msec - $this->start;
- $record->memory_usage = $usage;
- $record->memory_peak_usage = $peak_usage;
- $record->level = $level;
- $record->message = $message;
- $record->hint = $hint;
- // Run callbacks if FastPage instance passed.
- if ( isset($fastpage) ) {
- $fastpage->run_callbacks( 'debug_log', FastPage_Callback::all, $record );
- }
- $this->last = $record;
- $this->logs []= $record;
- // Pass through to error_log() if E_ERROR or E_USER_ERROR.
- if ( $level | E_ERROR | E_USER_ERROR ) {
- error_log( $message, $this->error_message_type, $this->error_destination );
- }
- $this->debug_out($in);
- return $record;
- }
- /**
- * Add new log record (short hand to singleton instance add_log).
- */
- public static function log( $fastpage, $level, $message, $hint = null ) {
- if ( !self::$instance ) return;
- self::$instance->add_log( $fastpage, $level, $message, $hint );
- }
- /**
- * Merge another debug for forking.
- *
- * @param FastPage_Debug $debug Debug object to merge.
- */
- public function merge( $debug ) {
- $in = $this->debug_in();
- // Make profiles unique.
- $profiles = array();
- foreach ( $this->logs as $log ) {
- if ( $log->level == E_FP_PROFILE ) {
- $profiles[$log->message] = true;
- }
- }
- foreach ( $debug->logs as $log ) {
- if ( $log->level == E_FP_PROFILE ) {
- if ( !isset($profiles[$log->message]) ) {
- $this->logs []= $log;
- }
- } else {
- $this->logs []= $log;
- }
- }
- // Sum debugging time.
- $this->debugging_time += $debug->debugging_time;
- $this->debug_out($in);
- }
- /**
- * Get log records.
- *
- * @param $mask Mask to filter log level like E_ALL.
- * @return array Log records. If not started logging, return empty array.
- */
- public function log_records( $mask = E_ALL ) {
- // If logging not started, do nothing.
- if ( !$this->enabled ) return array();
- $in = $this->debug_in();
- $records = array();
- $lasts = array();
- $sums = array();
- foreach ( $this->logs as $record ) {
- if ( $record->level & $mask ) {
- // Get delta and sum of timestamp and memory usage.
- foreach ( self::$numeric_log_members as $prop ) {
- $delta = 'delta_' . $prop;
- $sum = 'sum_' . $prop;
- $record->$delta = isset($lasts[$prop])? $record->$prop - $lasts[$prop]: 0;
- $lasts[$prop] = $record->$prop;
- $sums[$prop] = isset($sums[$prop])? $sums[$prop] + $record->$delta: $record->$delta;
- $record->$sum = $sums[$prop];
- }
- $records []= $record;
- }
- }
- $this->debug_out($in);
- return $records;
- }
- /**
- * Get profiling records.
- *
- * @return array Profiling results.
- */
- public function log_profiles() {
- // If logging not started, do nothing.
- if ( !$this->enabled ) return array();
- $in = $this->debug_in();
- // Gather profiles.
- $profiles = array();
- $last = $this->last;
- foreach ( $this->logs as $log ) {
- if ( $log->level != E_FP_PROFILE ) continue;
- if ( isset($profiles[$log->message]) ) {
- // Update profile result.
- $profile = $profiles[$log->message];
- foreach ( self::$numeric_log_members as $prop ) {
- $to = $prop . '_to';
- $from = $prop . '_from';
- $delta = 'delta_' . $prop;
- $profile->$to = $log->$prop;
- $profile->$delta = $profile->$to - $profile->$from;
- }
- } else {
- // New profile result.
- $profile = new FastPage_Profile;
- $profile->name = $log->message;
- foreach ( self::$numeric_log_members as $prop ) {
- $to = $prop . '_to';
- $from = $prop . '_from';
- $delta = 'delta_' . $prop;
- $profile->$from = $log->$prop;
- $profile->$to = $last->$prop;
- $profile->$delta = $profile->$to - $profile->$from;
- }
- $profiles[$log->message] = $profile;
- }
- }
- $this->debug_out($in);
- return $profiles;
- }
- }
- /**
- * Text filters.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- */
- class FastPage_TextFilters extends FastPage_Object {
- /**
- * Filter definitions.
- */
- protected $filters = array();
- /**
- * Add a new condition to filters.
- *
- * @param string $condition Condition string usable wildcard(*) and regular expressions starts with ~.
- * @param boolean $ignore_case Set true, if ignore charactors case.
- */
- public function add( $condition, $ignore_case = false, $options = null ) {
- $args = array( $condition, $ignore_case, $options );
- $this->filters []= $args;
- }
- /**
- * Test if match a text for each filters.
- *
- * @param string $text An string to test.
- * @return boolean True, if one of filters is matched at least.
- */
- public function match( $text ) {
- // Has no filters, return false.
- if ( count($this->filters) < 1 ) return false;
- // Evaluate conditions.
- foreach ( $this->filters as $array ) {
- // Filter properties.
- $filter = $array[0];
- if ( !isset($filter) ) continue;
- $ignore_case = $array[1];
- $options = $array[2];
- // Convert to regular expression.
- $regex = null;
- if ( substr( $filter, 0, 1 ) == '~' ) {
- // By regular expression.
- $regex = substr( $filter, 1 );
- } else if ( strchr( $filter, '*' ) !== false ) {
- // Include wildcard.
- $regex = str_replace( '*', '(.*?)', $filter );
- } else if ( $options ) {
- // Has options.
- $regex = '^' . preg_quote($filter) . '$';
- }
- if ( $regex ) {
- // Regex compare.
- if ( !isset($options) ) $options = '';
- if ( $ignore_case ) $options .= 'i';
- // Escape.
- $regex = str_replace( '!', '\!', $regex );
- // Regular expression matched?
- $regex = '!' . $regex . '!' . $options;
- if ( preg_match( $regex, $text ) ) return true;
- } else if ( $ignore_case ) {
- if ( strtolower($text) == strtolower($filter) ) return true;
- } else {
- // String compare.
- if ( $text == $filter ) return true;
- }
- }
- // Return false, if no match.
- return false;
- }
- }
- /**
- * Base of a helper class.
- *
- * Helper classes has a reference of FastPage instance to access FastPage configurations.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga@ideamans's Inc.
- */
- class FastPage_Helper extends FastPage_Object {
- /**
- * Constructor.
- *
- * @param FastPage $fastpage FastPage instance.
- */
- public function __construct( $fastpage ) {
- parent::__construct();
- // Store FastPage instance.
- $this->fastpage = $fastpage;
- }
- }
- /**
- * Plugin base class.
- *
- * Currently just an alias of FastPage_Helper.
- *
- * @var string $version Version of the plugin.
- */
- class FastPage_Plugin extends FastPage_Helper {
- /**
- * Constructor.
- */
- public function __construct( $fastpage ) {
- parent::__construct($fastpage);
- $this->version = '[UNKNOWN]';
- }
- }
- /**
- * Tree structured configuration.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- */
- class FastPage_Config extends FastPage_Helper {
- /**
- * Child configs.
- */
- protected $sub_configs = array();
- /**
- * Parent config.
- */
- public $parent_config = null;
- /**
- * Get a sub config.
- *
- * @param string $name Name of sub config.
- * @return FastPage_Config Sub config object. If not exist, return new object.
- */
- public function sub( $name ) {
- if ( !isset($this->sub_configs[$name]) ) {
- $class = get_class($this);
- $this->sub_configs[$name] = new $class( $this->fastpage );
- $this->sub_configs[$name]->parent_config = $this;
- }
- return $this->sub_configs[$name];
- }
- /**
- * Undefined function behaves as dictionary.
- */
- public function __call( $name, $args ) {
- $count = count($args);
- $key = $count > 0? $args[0]: null;
- $value = $count > 1? $args[1]: null;
- if ( $key ) {
- return $this->__dictionary( $name, $key, $value );
- }
- }
- /**
- * Set default value.
- *
- * Update self value if not set yet.
- *
- * @param string $name Name of config value.
- * @param mixed $value Default value of the config.
- */
- public function _default( $name, $value ) {
- $current = $this->$name;
- if ( !isset($current) ) {
- $this->$name = $value;
- }
- }
- }
- /**
- * Simple HTTP client helper.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga
- * @var string $library HTTP client library: builtin, curl or http_request.
- */
- class FastPage_SimpleHTTP extends FastPage_Helper {
- /**
- * Get url content.
- *
- * Return null if failed.
- *
- * @param string $url URL to get.
- * @return string Response body.
- */
- public function get( $url ) {
- // Detect library.
- $config_lib = $this->fastpage->config('http')->library;
- $config_lib = $config_lib? strtolower($config_lib): 'builtin';
- $enabled = array();
- if ( ini_get('allow_url_fopen') ) {
- $enabled['builtin'] = 'builtin';
- }
- if ( function_exists('curl_init') ) {
- $enabled['curl'] = 'curl';
- }
- $enabled['http_request'] = 'http_request';
- $this->library = $library = $enabled[$config_lib]? $config_lib: array_shift($enabled);
- // Timeout.
- $timeout = $this->fastpage->config('http')->timeout;
- if ( !$timeout ) $timeout = 60;
- $result = null;
- try {
- if ( $library == 'builtin' ) {
- // Use builtin function.
- $context = stream_context_create( array(
- 'http' => array(
- 'timeout' => $timeout,
- 'header' => array( 'Accept-Encoding:' )
- )
- ) );
- $result = file_get_contents( $url, false, $context );
- } else if ( $library == 'curl' ) {
- // Use cURL.
- $ch = curl_init( $url );
- $opts = array(
- CURLOPT_HEADER => false,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_BINARYTRANSFER => true,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_MAXREDIRS => 10,
- CURLOPT_SSL_VERIFYPEER => true,
- CURLOPT_CONNECTTIMEOUT => $timeout,
- CURLOPT_TIMEOUT => $timeout,
- CURLOPT_HTTPHEADER => array( 'Accept-Encoding:' )
- );
- // CURLOPT_MUTE maybe deprecated.
- if ( defined('CURLOPT_MUTE') ) $opts[CURLOPT_MUTE] = true;
- foreach ( $opts as $key => $value ) {
- curl_setopt( $ch, $key, $value );
- }
- $result = curl_exec($ch);
- curl_close($ch);
- } else {
- // Try to use Pear HTTP_Request.
- require_once('HTTP/Request.php');
- $opts = array(
- 'timeout' => $timeout,
- 'allowRedirects' => true,
- 'maxRedirects' => 10
- );
- $http = new HTTP_Request( $url, $opts );
- $http->removeHeader('accept-encoding');
- $res = $http->sendRequest();
- if ( !PEAR::isError($res) ) {
- $result = $http->getResponseBody();
- }
- }
- } catch ( Exception $ex ) {
- FastPage_Debug::log( $this->fastpage, E_USER_ERROR, "Simple HTTP get $url because: " . $ex->getMessage() );
- }
- return $result;
- }
- }
- /**
- * User agent helper.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var string $full_string Full user agent string.
- * @var boolean $data_uri_enabled If data URI schema is enabled on the user agent.
- */
- class FastPage_UserAgent extends FastPage_Helper {
- /**
- * Constructor.
- */
- public function __construct( $fastpage, $ua = null ) {
- parent::__construct($fastpage);
- if ( !isset($ua) ) {
- // Detect user agent.
- $ua = FastPage_Util::server( 'HTTP_USER_AGENT', '' );
- }
- // Default.
- $this->full_string = $ua;
- $this->data_uri_enabled = false;
- // Data URI scheme ability.
- // Supported on WebKit, Gecko, Opera and IE8+.
- if ( preg_match( '!WebKit|Gecko|Opera!i', $ua ) ) {
- $this->data_uri_enabled = true;
- } else if ( preg_match( '!MSIE ([0-9\.]+)!', $ua, $matches ) ) {
- $version = (float)$matches[1];
- if ( $version >= 8.0 ) {
- $this->data_uri_enabled = true;
- }
- }
- // Run callbacks.
- $fastpage->run_callbacks( 'parse_user_agent', FastPage_Callback::all, $this );
- // Logging.
- if ( !$this->data_uri_enabled ) {
- FastPage_Debug::log( $fastpage, E_USER_NOTICE, 'The use agent does not support Data URI scheme: ' . $ua );
- }
- }
- }
- /**
- * A helper class to parse and modify an HTML element simply.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga@ideaman's Inc.
- */
- class FastPage_HTMLElement extends FastPage_Helper {
- public $_attribute = array();
- public $_attributes = null;
- // Common regular expressions.
- const attribute_regex_prefix = '!(?<=\\s';
- const attribute_regex_suffix = '=)(["\'])(.*?)(?=\\1|>)!i';
- const remove_attribute_regex_prefix = '!(?<=\\s)';
- const remove_attribute_regex_suffix = '=(["\'])(.*?)(\\1\\s*)!i';
- const attributes_regex = '!(?<=\\s)([^=]+)=(["\'])(.*?)(?=\\2|>)!i';
- /**
- * Constructor.
- *
- * @param FastPage $fastpage An instance of FastPage class.
- * @param string $fragment A source of HTML element fragment.
- */
- public function __construct( $fastpage, $fragment ) {
- parent::__construct($fastpage);
- // Initialize.
- $this->original = $this->html = $fragment;
- // Check format.
- if ( substr( $this->html, 0, 1 ) != '<' || substr( $this->html, -1, 1 ) != '>' ) {
- FastPage_Debug::log( $fastpage, E_USER_WARNING, 'Not a wellformed HTML element: ' . $this->html );
- throw new FastPage_Exception( 'Not a wellformed HTML element: ' . $this->html );
- }
- // Parse tag name.
- if ( preg_match( '!^</?([^\s]+)!', $this->html, $matches ) ) {
- $this->tag_name = $matches[1];
- }
- }
- /**
- * Gets values of an attribute.
- *
- * @param string $name Name of an attribute.
- * @param string $value Value of the attribute. If set null, this function behaves as a getter.
- * @return string Value of the attribute. If not found, returns null.
- */
- public function attribute($name, $value = null) {
- // Regulara expression to find an atribute.
- $regex = self::attribute_regex_prefix . preg_quote($name) . self::attribute_regex_suffix;
- if ( isset($value) ) {
- // Replace attribute value.
- $this->html = preg_replace( $regex, "\\1$value", $this->html, -1, $count );
- // Cache the result if replaced.
- if ( $count > 0 ) {
- $this->_attribute[$name] = $value;
- $this->_attributes = null;
- }
- } else {
- // Get attribute value.
- if ( !isset($this->_attribute[$name]) ) {
- // Not cached, find the value from HTML.
- if ( preg_match( $regex, $this->html, $matches ) ) {
- $this->_attribute[$name] = $matches[2];
- } else {
- // Not found, cache false as boolean.
- $this->_attribute[$name] = false;
- }
- }
- $value = $this->_attribute[$name];
- return $value !== false? $value: null;
- }
- }
- /**
- * Remove an attribute.
- *
- * @param string $name Name of an attribute to remove.
- */
- public function remove_attribute($name) {
- // Regulara expression to find an atribute.
- $regex = self::remove_attribute_regex_prefix . preg_quote($name) . self::remove_attribute_regex_suffix;
- $this->html = preg_replace( $regex, '', $this->html, -1, $count );
- // Cache the result if replaced.
- if ( $count > 0 ) {
- unset($this->_attribute[$name]);
- $this->_attributes = null;
- }
- }
- /**
- * Gets all attribute as an array.
- *
- * @return Attributes name and values as an array.
- */
- public function attributes() {
- if ( !isset($this->_attributes) ) {
- // Not cached, find with regular expression.
- $this->_attributes = array();
- if ( preg_match_all( self::attributes_regex, $this->html, $matches, PREG_SET_ORDER ) ) {
- foreach ( $matches as $match ) {
- $this->_attributes[$match[1]] = $match[3];
- }
- }
- }
- return $this->_attributes;
- }
- }
- /**
- * Cache helper.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var integer $default_expires Default second to expire cache.
- */
- class FastPage_Cache extends FastPage_Helper {
- protected $_cache = array();
- /**
- * Constructor.
- */
- public function __construct( $fastpage ) {
- parent::__construct($fastpage);
- $config = $this->fastpage->config('cache');
- // Max key length.
- $this->max_key_length = $config->max_key_length;
- if ( !isset($this->max_key_length) ) $this->max_key_length = 128;
- // Cache key normalization algorithm.
- $this->normalize_key_algorithm = $config->normalize_key_algorithm;
- if ( !isset($this->normalize_key_algorithm) ) $this->normalize_key_algorithm = 'md5';
- // Default expire.
- $this->default_expire = $config->default_expire;
- if ( !isset($this->default_expire) ) $this->default_expire = 60 * 60; // 1 hour
- }
- /**
- * Normalize cache key.
- *
- * Convert long key with md5 or sha1.
- *
- * @param string $raw_key Raw cache key.
- * @return string Normalized key.
- */
- public function normalize_key( $raw_key ) {
- if ( strlen($raw_key) > $this->max_key_length ) {
- $algorithm = $this->normalize_key_algorithm;
- if ( $algorithm != 'sha1' ) $algorithm = 'md5';
- $key = $algorithm($raw_key);
- } else {
- $key = $raw_key;
- }
- return $key;
- }
- /**
- * Check if exist key.
- *
- * @param string $namespace Namespace.
- * @param string $raw_key Cache key.
- * @return boolean True if exists key.
- */
- public function exist( $namespace, $raw_key ) {
- if ( !array_key_exists( $namespace, $this->_cache ) ) {
- return false;
- }
- // Check array.
- $key = $this->normalize_key($raw_key);
- return array_key_exists( $key, $this->_cache[$namespace] );
- }
- /**
- * Get cached value.
- *
- * * This is reference implementation not effective as cache.
- *
- * @param string $namespace Namespace of cache.
- * @param string $key Key of cache item.
- * @return mixed Cached value. If not cached, return null.
- */
- public function get( $namespace, $raw_key ) {
- // Convert a key.
- $key = $this->normalize_key($raw_key);
- // Search cached value.
- if ( isset($this->_cache[$namespace]) ) {
- if ( isset($this->_cache[$namespace][$key]) ) {
- $entry = $this->_cache[$namespace][$key];
- if ( $entry['expire'] > time() ) {
- return $entry['value'];
- } else {
- unset( $this->_cache[$namespace][$key] );
- }
- }
- }
- return null;
- }
- /**
- * Set cache value.
- *
- * * This is reference implementation not effective as cache.
- *
- * @param string $namespace Namespace of cache.
- * @param string $raw_key Key of cache entry.
- * @param mixed $value Value of cache entry.
- * @param integer $expire Second to expire the cache. If null, use default_expire.
- */
- public function set( $namespace, $raw_key, $value, $expire = null ) {
- // Expire.
- if ( !isset($expire) ) {
- $expire = $this->default_expire;
- }
- // Convert a key.
- $key = $this->normalize_key($raw_key);
- $this->_cache[$namespace][$key] = array( 'expire' => time() + $expire, 'value' => $value );
- }
- /**
- * Delete value.
- *
- * @param string $namespace Namespace.
- * @param string $raw_key Cache key.
- */
- public function delete( $namespace, $raw_key ) {
- if ( !isset($this->_cache[$namespace]) ) return;
- $key = $this->normalize_key($raw_key);
- unset( $this->_cache[$namespace][$key] );
- }
- /**
- * Flush all cache entries.
- *
- * * This is reference implementation not effective as cache.
- *
- * @param string $namespace Namespace of cache. If null, clear all cache.
- */
- public function flush( $namespace = null ) {
- if ( isset($namespace) ) {
- $this->_cache[$namespace] = array();
- } else {
- $this->_cache = array();
- }
- }
- /**
- * Purge expired cache.
- *
- * * This is reference implementation not effective as cache.
- *
- * @param string $namespace Namespace of cache. If null, purge all cache.
- */
- public function purge( $namespace = null ) {
- if ( isset($namespace) ) {
- if ( is_array($this->_cache[$namespace]) ) {
- $now = time();
- foreach ( $this->_cache[$namespace] as $key => $entry ) {
- if ( $entry['expire'] <= $now ) {
- unset( $this->_cache[$namespace][$key] );
- }
- }
- }
- } else {
- foreach ( $this->_cache as $ns => $caches ) {
- $this->purge( $ns );
- }
- }
- }
- }
- /**
- * File cache helper.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var string $extension An extension of cache file.
- */
- class FastPage_FileCache extends FastPage_Cache {
- protected $paths_to_ready = array();
- /**
- * Constructor.
- *
- * Check if ready to use directory.
- */
- public function __construct( $fastpage ) {
- parent::__construct($fastpage);
- $config = $this->fastpage->config('cache');
- // Cache dir.
- $this->cache_dir = $config->cache_dir;
- if ( !isset($this->cache_dir) ) $this->cache_dir = './cache';
- // Cache key index charactors, default is 2.
- $this->key_index_chars = $config->key_index_chars;
- if ( !isset($this->key_index_chars) || !$this->key_index_chars )
- $this->key_index_chars = 2;
- // Extension.
- $this->extension = $config->extension;
- if ( !isset($this->extension) || strlen($this->extension) < 1 )
- $this->extension = 'cache';
- // Permissions.
- $this->file_perms = $config->file_perms;
- if ( !isset($this->file_perms) ) $this->file_perms = 0644;
- $this->dir_perms = $config->dir_perms;
- if ( !isset($this->dir_perms) ) $this->dir_perms = 0755;
- // Ready to use paths.
- $this->paths_to_ready = array();
- // Ready?
- $this->ready();
- }
- /**
- * Check if ready to use.
- *
- * Check existence, directory and writable.
- */
- protected function ready( $namespace = null, $key_index = null ) {
- $path = $this->cache_dir;
- if ( isset($namespace) ) {
- if ( isset($key_index) ) {
- $path = FastPage_Util::cat_path( $path, urlencode($namespace), $key_index );
- } else {
- $path = FastPage_Util::cat_path( $path, urlencode($namespace) );
- }
- }
- // Lookup cache.
- if ( isset($this->paths_to_ready[$path]) ) return;
- if ( file_exists($path) && is_dir($path) && is_writable($path) ) {
- // Ready to use.
- $this->paths_to_ready[$path] = true;
- return;
- }
- if ( file_exists($path) ) {
- // Is directory?
- if ( !is_dir($path) ) {
- throw new FastPage_Exception( "Path: $path is already exists as not a directory." );
- }
- // Is writable?
- if ( !is_writable($path) ) {
- throw new FastPage_Exception( "Path: $path is not writable." );
- }
- } else {
- // Try to make directory.
- if ( false === mkdir( $path, $this->dir_perms, true ) ) {
- throw new FastPage_Exception( "Can not create $path as directory." );
- }
- }
- // Mark as ready.
- $this->paths_to_ready[$path] = true;
- }
- /**
- * Get index of key.
- */
- protected function key_index( $key ) {
- $index = substr( $key, 0, $this->key_index_chars );
- $index = str_pad( $index, $this->key_index_chars, '_' );
- return $index;
- }
- /**
- * Get file path for key.
- */
- protected function cache_path( $namespace, $raw_key ) {
- // Normalize the key.
- $key = $this->normalize_key($raw_key);
- // Build cache path.
- // Replace periods for security reason.
- $file = urlencode($key);
- $file = str_replace( '.', '%2e', $file );
- $index = $this->key_index( $file );
- if ( $this->extension ) $file .= '.' . $this->extension;
- // Ready to use?
- $this->ready( $namespace );
- $this->ready( $namespace, $index );
- return FastPage_Util::cat_path( $this->cache_dir, urlencode($namespace), $index, $file );
- }
- /**
- * Override exist method.
- */
- public function exist( $namespace, $raw_key ) {
- $path = $this->cache_path( $namespace, $raw_key );
- return file_exists($path);
- }
- /**
- * Override get method.
- */
- public function get( $namespace, $raw_key ) {
- $path = $this->cache_path( $namespace, $raw_key );
- if ( file_exists($path) && is_file($path) && is_readable($path) ) {
- if ( filemtime($path) > time() ) return @file_get_contents($path);
- @unlink($path);
- }
- return null;
- }
- /**
- * Override set method.
- */
- public function set( $namespace, $raw_key, $value, $expire = null ) {
- // Expires.
- if ( !isset($expire) ) {
- $expire = $this->default_expire;
- }
- // Put a file.
- $path = $this->cache_path( $namespace, $raw_key );
- @file_put_contents( $path, $value );
- if ( file_exists($path) ) {
- @chmod( $path, $this->file_perms );
- @touch( $path, time() + $expire );
- }
- }
- /**
- * Override delete method.
- */
- public function delete( $namespace, $raw_key ) {
- $path = $this->cache_path( $namespace, $raw_key );
- if ( file_exists($path) && is_file($path) ) {
- @unlink($path);
- $dir = dirname($path);
- $dh = opendir( $dir );
- $entries = 0;
- while ( false !== ( $file = readdir($dh) ) ) {
- if ( $file != '.' && $file != '..' ) {
- $entries++;
- break;
- }
- }
- closedir($dh);
- if ( $entries == 0 ) @rmdir($dir);
- }
- }
- /**
- * Clear files and directories recursively.
- */
- protected function clear_recursive( $dir, $expire = null ) {
- if ( !file_exists($dir) || !is_dir($dir) ) return;
- $dh = opendir($dir);
- $entries = 0;
- while ( false !== ( $file = readdir($dh) ) ) {
- // Skip . or ..
- if ( $file == '.' || $file == '..' )
- continue;
- // Increment at once.
- $entries++;
- $path = FastPage_Util::cat_path( $dir, $file );
- if ( is_file( $path ) ) {
- // Skip if not expired.
- if ( isset($expire) && filemtime($path) > $expire )
- continue;
- // Delete a file.
- if ( @unlink($path) ) $entries--;
- } else if ( is_dir( $path ) ) {
- // Clear resursively.
- $sub_entries = $this->clear_recursive( $path, $expire );
- if ( $sub_entries == 0 ) {
- // Remove directory if empty.
- if ( @rmdir($path) ) $entries--;
- }
- }
- }
- closedir($dh);
- return $entries;
- }
- /**
- * Flush and purge proxy.
- */
- public function clear( $namespace = null, $expire = null ) {
- $dir = $this->cache_dir;
- if ( isset($namespace) ) $dir = FastPage_Util::cat_path( $dir, urlencode($namespace) );
- $this->clear_recursive( $dir, $expire );
- }
- /**
- * Override flush method.
- */
- public function flush( $namespace = null ) {
- $this->clear( $namespace, null );
- }
- /**
- * Override clear method.
- */
- public function purge( $namespace = null ) {
- $this->clear( $namespace, time() );
- }
- }
- /**
- * Callback object.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga@ideamans.com
- * @var FastPage_Core $fastpage FastPage_Core instance.
- * @var string $event Callback event.
- * @var integer $priority Callback priority.
- * @var mixed $function Callable function as string, or array of object and method..
- * @var mixed $hint Callback hint.
- * @var integer $index Callback index in priority chain.
- */
- class FastPage_Callback extends FastPage_Object {
- /**
- * Callback option: Run only the first callback returns a value not null.
- */
- const first_one = 0x01;
- /**
- * Callback option: Run all callbacks and return nothing.
- */
- const all = 0x02;
- /**
- * Callback option: Run all callbacks and return those results that is not null as an array.
- */
- const results_array = 0x04;
- /**
- * Callback option: Run all callbacks and operate logically with 'OR'.
- */
- const results_and = 0x08;
- /**
- * Callback option: Run all callbacks and operate logically with 'AND'.
- */
- const results_or = 0x10;
- /**
- * Constructor.
- *
- * @param FastPage_Core $fastpage FastPage_Core object contains the callback.
- * @param string $event Callback event.
- * @param integer $priority Callback priority.
- * @param mixed $function Callable function.
- * @param mixed $hint Callback hint.
- */
- public function __construct( $fastpage, $event, $priority, $function, $hint ) {
- $this->fastpage = $fastpage;
- $this->event = $event;
- $this->priority = $priority;
- $this->function = $function;
- $this->hint = $hint;
- }
- }
- /**
- * Onetime task object.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var FastPage_Core $fastpage FastPage_Core instance.
- * @var string $name Task name.
- * @var integer $index Index in task queue.
- * @var mixed $function Callable function as string, or array of object and method.
- * @var mixed $args Arguments to call function.
- * @var mixed $result Task result.
- */
- class FastPage_OnetimeTask extends FastPage_Object {
- /**
- * Constructor.
- *
- * @param FastPage_Core $fastpage FastPage_Core object queueing the task.
- * @param string $name Name of the task.
- * @param mixed $function Task function.
- * @param mixed $args Arguments to call function.
- */
- public function __construct( $fastpage, $name, $function, $args ) {
- $this->fastpage = $fastpage;
- $this->name = $name;
- $this->function = $function;
- $this->args = $args;
- }
- }
- /**
- * Core features of FastPage like callback and helpers system.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga@ideaman's Inc.
- */
- class FastPage_Core extends FastPage_Object {
- /**
- * Callbacks chain.
- */
- protected $_callbacks = array();
- /**
- * Onetime tasks queue.
- */
- protected $_onetime_tasks = array();
- /**
- * Helper singleton instances.
- */
- protected $_helper_instance = array();
- /**
- * Plugins.
- */
- protected $_plugins = array();
- /**
- * Constructor
- */
- public function __construct() {
- parent::__construct();
- // Built-in helpers.
- $helpers = array(
- 'config' => 'Config',
- 'cache' => 'FileCache',
- 'html_element' => 'HTMLElement',
- 'user_agent' => 'UserAgent',
- 'simple_http' => 'SimpleHTTP'
- );
- foreach ( $helpers as $helper => $class ) {
- $this->helper_class( $helper, 'FastPage_' . $class );
- }
- // MIME types.
- $mime_types = array(
- 'jpeg' => 'image/jpeg',
- 'jpg' => 'image/jpeg',
- 'gif' => 'image/gif',
- 'png' => 'image/png',
- 'css' => 'text/css',
- 'js' => 'text/javascript',
- 'html' => 'text/html',
- 'htm' => 'text/html'
- );
- foreach ( $mime_types as $name => $value ) {
- $this->config()->mime_type( $name, $value );
- }
- }
- /*
- * Helper class accessor.
- *
- */
- public function helper_class( $name, $value = null ) {
- // Remove current singleton instance.
- unset( $this->_helper_instance[$name] );
- return $this->__dictionary( 'helper_class', $name, $value );
- }
- /**
- * Create new helper object.
- *
- * @param Array $name Name of helper.
- * @param boolean $singleton Optional. If true, the instance will be stored and reused as singleton.
- * @param mixed $param1 Optional. The first parameter for constructor.
- * @param mixed $param2 Optional. The second parameter for constructor.
- * @return mixed A new object of helper class.
- */
- public function helper_instance( $helper, $singleton = false, $param1 = null, $param2 = null ) {
- // If singleton and already exists, return the instance.
- if ( $singleton && array_key_exists( $helper, $this->_helper_instance ) ) {
- return $this->_helper_instance[$helper];
- }
- // Helper class.
- $class = $this->helper_class($helper);
- // Create instance with params.
- // FIXME: Which is a better way?
- $obj = null;
- if ( class_exists($class) ) {
- if ( isset($param1) && isset($param2) ) {
- $obj = new $class( $this, $param1, $param2 );
- } else if ( isset($param1) ) {
- $obj = new $class( $this, $param1 );
- } else {
- $obj = new $class( $this );
- }
- }
- // Store instance if singleton.
- if ( $singleton ) {
- $this->_helper_instance[$helper] = $obj;
- }
- // Run callbacks.
- $this->run_callbacks( 'helper_created.' . $helper, FastPage_Callback::all, $obj );
- return $obj;
- }
- /**
- * Document root by host.
- */
- public function document_root( $host = '', $value = null ) {
- // Is as default?
- if ( $host == '' || $host == $this->config()->default_host ) {
- if ( isset($value) )
- return $this->config()->default_document_root = $value;
- else
- return $this->config()->default_document_root;
- }
- return $this->config()->document_root( $host, $value );
- }
- /**
- * MIME type
- */
- public function mime_type( $ext, $value = null ) {
- return $this->config()->mime_type( $ext, $value );
- }
- /**
- * Add callback to chain.
- *
- * Callback has an event name and priority.
- * Callback will be called by order priority value is smaller in each event.
- *
- * @param string $event Name of an event.
- * @param integer $priority Priority of the callback.
- * @param mixed $function Function locator: Name of the function as a string or array of an object and method name.
- * @param mixed $hint Hint value that will be passed to calling.
- * @return FastPage_Callback Callback object added to chain.
- */
- public function add_callback( $event, $priority, $function, $hint = null ) {
- // Ensure chain.
- if ( !isset($this->_callbacks[$event][$priority]) ) {
- $this->_callbacks[$event][$priority] = array();
- }
- // Create callback object and push to chain.
- $callback = new FastPage_Callback( $this, $event, $priority, $function, $hint );
- $callback->index = array_push( $this->_callbacks[$event][$priority], $callback );
- return $callback;
- }
- /**
- * Remove callback.
- *
- * @param FastPage_Callback $callback Callback object.
- */
- public function remove_callback( $callback ) {
- if ( isset( $this->_callbacks[$callback->event][$callback->priority][$callback->index] ) ) {
- unset( $this->_callbacks[$callback->event][$callback->priority][$callback->index] );
- }
- }
- /**
- * Run callbacks.
- *
- * @param string $event Name of an event.
- * @param integer $flags Options to run callbacks chain.
- * @param mixed $args Argument to run callback.
- */
- public function run_callbacks( $event, $flags, $args = null ) {
- if ( isset($this->_callbacks[$event]) ) {
- // Sort priorities.
- $priorities = array_keys($this->_callbacks[$event]);
- sort( $priorities, SORT_NUMERIC );
- // Call functions.
- $result = null;
- foreach ( $priorities as $priority ) {
- $callbacks = $this->_callbacks[$event][$priority];
- foreach ( $callbacks as $callback ) {
- $function = $callback->function;
- $hint = $callback->hint;
- // Call function.
- if ( !is_callable($function) )
- continue;
- $r = call_user_func( $function, $callback, $args, $hint );
- // Treat result.
- if ( isset($r) ) {
- if ( $flags & FastPage_Callback::first_one ) {
- return $r;
- } else if ( $flags & FastPage_Callback::all ) {
- if ( !isset($result) ) $result = 0;
- $result++;
- } else if ( $flags & FastPage_Callback::results_array ) {
- if ( !isset($result) ) $result = array();
- $result []= $r;
- } else if ( $flags & FastPage_Callback::results_and ) {
- $result = isset($result)? $result && $r: $r;
- } else if ( $flags & FastPage_Callback::results_or ) {
- $result = isset($result)? $result || $r: $r;
- }
- }
- }
- }
- return $result;
- }
- return null;
- }
- /**
- * Add a new task to queue.
- */
- public function add_onetime_task( $name, $function, $args = null ) {
- // Cannot add lambda.
- if ( is_string($function) && preg_match( '!^\\x00lambda_!', $function ) ) {
- throw new FastPage_Exception( 'Onetime task can not be lambda by create_function.' );
- }
- // Add task to queue.
- $task = new FastPage_OnetimeTask( $this, $name, $function, $args );
- $task->index = array_push( $this->_onetime_tasks, $task );
- return $task;
- }
- /**
- * Remove onetime task from queue.
- */
- public function remove_onetime_task( $task ) {
- if ( isset($task->index) && isset($this->_tasks[$task->index]) ) {
- unset( $this->_onetime_tasks[$task->index] );
- }
- }
- /**
- * Check if has tasks in queue.
- */
- public function has_onetime_tasks() {
- return count($this->_onetime_tasks)? true: false;
- }
- /**
- * Run onetime tasks.
- */
- public function run_onetime_tasks() {
- foreach ( $this->_onetime_tasks as $task ) {
- $pofile = 'Task: ' . $task->name;
- FastPage_Debug::log( $this, E_FP_PROFILE, $pofile, $task );
- $task->result = call_user_func( $task->function, $task, $task->args );
- FastPage_Debug::log( $this, E_FP_PROFILE, $pofile, $task );
- }
- }
- /**
- * Save onetime tasks to cache.
- *
- * @return string Cache key of saved onetime tasks.
- */
- public function save_onetime_tasks() {
- $cache = $this->cache();
- $serialized = serialize($this->_onetime_tasks);
- $key = md5($serialized);
- $cache->set( 'onetime_tasks', $key, $serialized );
- return $key;
- }
- /**
- * Load onetime tasks from cache.
- *
- * @param string $key Cache key of saved onetime tasks.
- */
- public function load_onetime_tasks( $key ) {
- $cache = $this->cache();
- $serialized = $cache->get( 'onetime_tasks', $key );
- $cache->delete( 'onetime_tasks', $key );
- if ( !$serialized ) return;
- $tasks = @unserialize($serialized);
- if ( !is_array($tasks) ) return;
- foreach ( $tasks as $task ) {
- if ( is_a( $task, 'FastPage_OnetimeTask' ) ) {
- $task->index = array_push( $this->_onetime_tasks, $task );
- }
- }
- }
- /**
- * Run regular tasks.
- */
- public function run_regular_tasks() {
- $this->run_callbacks( 'regular_tasks', FastPage_Callback::all );
- }
- /**
- * Returns the config helper object.
- *
- * @param strings $ARGS Path to sub config.
- * @return FastPage_Config Configuration object.
- */
- public function config() {
- $config = $this->helper_instance( 'config', true );
- if ( func_num_args() > 0 ) {
- // Traverse sub configs.
- $path = func_get_args();
- foreach ( $path as $sub ) {
- if ( !is_string($sub) ) break;
- $config = $config->sub($sub);
- }
- }
- return $config;
- }
- /**
- * Returns the cache helper object.
- *
- * @return FastPage_Cache Caching object.
- */
- public function cache() {
- return $this->helper_instance( 'cache', true );
- }
- /**
- * Returns a new simple HTTP client helper object.
- *
- * @return FastPage_SimpleHTTP Simple HTTP object.
- */
- public function simple_http() {
- return $this->helper_instance( 'simple_http', false );
- }
- /**
- * Returns a new FastPage_HTMLElement object to parse a fragment.
- *
- * @param string $fragment HTML source fragment to parse.
- * @return FastPage_HTMLElement object to parse and modify the element.
- */
- public function parse_html_element( $fragment ) {
- try {
- return $this->helper_instance( 'html_element', false, $fragment );
- } catch ( FastPage_Exception $e ) {
- return null;
- }
- }
- /**
- * Get plugin instance.
- *
- * @param string $name Name of plugin.
- * @return mixed Plugin instance.
- */
- public function plugin( $name ) {
- if ( isset($this->_plugins[$name]) ) {
- return $this->_plugins[$name];
- }
- return false;
- }
- /**
- * Load plugin.
- *
- * @param string $name Name of plugin.
- * @param mixed $args Argument passed to plugin function.
- * @return mixed Return value of plugin function. If not defined plugin function, return null.
- */
- public function load_plugin( $name, $args = null ) {
- // Already loaded?
- if ( $plugin = $this->plugin($name) ) return $plugin;
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Load plugin: ' . $name );
- $plugin_dir = $this->config()->plugin_dir;
- if ( !isset($plugin_dir) ) {
- // Initialize plugin directory path.
- $base_dir = dirname(__FILE__);
- $plugin_dir = FastPage_Util::cat_path( $base_dir, '../plugins');
- }
- // Load plugin file.
- $plugin_path = FastPage_Util::cat_path( $plugin_dir, "$name.php" );
- if ( file_exists($plugin_path) ) {
- require_once($plugin_path);
- $class_prefix = 'FastPage_Plugin_';
- // Create an instance of plugin class.
- $class_names = array( $class_prefix . $name, $name );
- foreach ( $class_names as $class ) {
- if ( class_exists($class) ) {
- $plugin = $this->_plugins[$name] = new $class( $this, $args );
- // Callback.
- $this->run_callbacks( 'plugin_loaded', FastPage_Callback::all, $plugin );
- FastPage_Debug::log( $this, E_USER_NOTICE, "Plugin loaded: $name Ver." . $plugin->version );
- break;
- }
- // Plugin class not found.
- if ( !$plugin ) {
- FastPage_Debug::log( $this, E_USER_ERROR, 'Plugin class ' . $class . ' does not exist.' );
- }
- }
- } else {
- // Plugin file not found.
- FastPage_Debug::log( $this, E_USER_ERROR, 'Plugin file ' . $plugin_path . ' does not exist.' );
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Load plugin: ' . $name );
- return $plugin;
- }
- /**
- * Load config file.
- */
- public function load_config_file( $config_path = null ) {
- if ( $config_path === false ) return;
- // Load config file.
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Load config file' );
- if ( !isset($config_path) ) {
- // Auto detection.
- $config_path = './config.php';
- if ( !file_exists($config_path) ) {
- $script_path = FastPage_Util::server('SCRIPT_FILENAME');
- if ( !isset($script_path) ) {
- $script_path = __FILE__;
- }
- $script_dir = dirname($script_path);
- $config_path = FastPage_Util::cat_path( $script_dir, 'config.php' );
- }
- }
- // Load config.
- if ( $config_path !== false ) {
- if ( file_exists($config_path) ) {
- $fastpage = $app = $this;
- include($config_path);
- } else {
- FastPage_Debug::log( $this, E_USER_ERROR, 'Can not load config file: ' . $config_path );
- }
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Load config file' );
- }
- }
- /**
- * FastPage request.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var string $method Request method.
- * @var string $url Reqeust URL.
- * @var string $path Target Path.
- * @var string $host Host name.
- */
- class FastPage_Request extends FastPage_Helper { }
- /**
- * FastPage response.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- */
- class FastPage_Response extends FastPage_Helper {
- public $headers = array();
- /**
- * Build HTTP headers with properties.
- *
- * @return array HTTP headers.
- */
- public function build_headers() {
- $headers = $this->headers;
- // Content length.
- if ( $this->body ) {
- $headers['Content-Length'] = strlen($this->body);
- }
- // Etag.
- if ( $this->etag ) {
- $headers['ETag'] = FastPage_Util::quote($this->etag);
- }
- // Content Type.
- if ( $this->content_type ) {
- $content_type = $this->content_type;
- if ( $this->charset && false === strstr( strtolower($content_type), 'charset=' ) ) {
- $content_type .= ';charset=' . $this->charset;
- }
- $headers['Content-Type'] = $content_type;
- }
- return $headers;
- }
- };
- /**
- * FastPage processing context.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- *
- * @var FastPage_Request $request
- */
- class FastPage_Context extends FastPage_Helper {
- public function __construct($fastpage) {
- parent::__construct($fastpage);
- // Default values.
- $this->requires_etag = true;
- // Request and response.
- $this->request = $fastpage->helper_instance( 'request', false );
- $this->response = $fastpage->helper_instance( 'response', false );
- $this->user_agent = $fastpage->helper_instance( 'user_agent', false );
- }
- /**
- * Check if match Etag of request and result.
- *
- * @return boolean If matched, return true.
- */
- public function match_etag() {
- // No need etag.
- if ( !$this->requires_etag ) {
- return false;
- }
- // Generate etag.
- if ( !$this->response->etag ) {
- $this->response->etag = $this->fastpage->generate_etag( $this );
- }
- // Compare etag.
- if ( $this->request->etag && $this->response->etag && $this->request->etag == $this->response->etag ) {
- $this->not_modified = true;
- return true;
- }
- return false;
- }
- }
- /**
- * External file referrence like image or JavaScript.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var string $url Original URL.
- * @var string $path Real path.
- * @var string $domain Domain of the external.
- * @var string $relative_path Relative path from the document root.
- * @var string $mime_type MIME type.
- * @var string $replace_type Type of replacement like data_uri or inline.
- * @var string $replace Replacement.
- * @var mixed $index Index in the set.
- * @var integer $redundancy Redudancy in the set.
- */
- class FastPage_External extends FastPage_Object {
- /**
- * Etag of the file.
- *
- * @param boolean $quote If true, double-quote the result.
- * @return string Etag of the file.
- */
- public function etag( $quote = true ) {
- if ( isset($this->etag) ) {
- // Already gotten.
- return $this->etag;
- }
- if ( $this->path ) {
- // Get etag.
- $this->etag = FastPage_Util::file_etag( $this->path, $quote );
- if ( !$this->etag ) $this->etag = false;
- return $this->etag;
- }
- return null;
- }
- /**
- * Convert to Data URI schema.
- */
- public function data_uri() {
- if ( $this->mime_type && file_exists($this->path) ) {
- if ( false !== ( $contents = file_get_contents($this->path) ) ) {
- $data_uri = 'data:' . $this->mime_type . ';base64,';
- $data_uri .= base64_encode($contents);
- return $data_uri;
- }
- }
- return null;
- }
- /**
- * Check if the file is image.
- */
- public function is_image() {
- if ( strtolower( substr( $this->mime_type, 0, 6 ) ) == 'image/' ) {
- return true;
- }
- return false;
- }
- /**
- * Check if the file is JavaScript.
- */
- public function is_js() {
- if ( strtolower( $this->mime_type ) == 'text/javascript' ) {
- return true;
- }
- return false;
- }
- }
- /**
- * Collection of an external referrence.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var string $base_dir Base directory path.
- * @var array $externals Array collection of FastPage_External object.
- */
- class FastPage_Externals extends FastPage_Helper {
- public $externals = array();
- /**
- * Constructor.
- *
- * @param FastPage $fastpage FastPage object.
- * @param array $urls URLs to make collection.
- * @param string $base_dir Base directory path for the set.
- */
- public function __construct( $fastpage, $context, $urls ) {
- FastPage_Helper::__construct($fastpage);
- $this->base_dir = $context->response->base_dir;
- // Parse URLs.
- $config = $fastpage->config();
- $redundancies = array();
- foreach ( $urls as $index => $url ) {
- // Skip excluded url.
- if ( $config->exclude_url->match($url) ) continue;
- // Get path, but skip if it matches excluded path.
- $domain = $context->request->host;
- $relative_path = '';
- $path = $fastpage->url_to_path( $url, $this->base_dir, true, $domain, $relative_path );
- $mime_type = '';
- if ( $path && is_file($path) ) {
- if ( $config->exclude_path->match($path) ) continue;
- // Mime type.
- $pi = pathinfo($path);
- $ext = $pi['extension'];
- $mime_type = $config->mime_type($ext);
- // Count up redundancies.
- if ( !isset($redundancies[$path]) ) {
- $redundancies[$path] = 0;
- }
- $redundancies[$path]++;
- }
- // New reference.
- $external = new FastPage_External;
- $external->url = $url;
- $external->path = $path;
- $external->domain = $domain;
- $external->relative_path = $relative_path;
- $external->mime_type = $mime_type;
- $external->index = $index;
- // Add to array.
- $this->externals[$index] = $external;
- }
- // Apply redundancies.
- foreach ( $this->externals as $external ) {
- if ( $external->path && isset($redundancies[$external->path]) ) {
- $external->redundancy = $redundancies[$external->path];
- }
- }
- }
- /**
- * Force to free external object.
- *
- * @param integer $index Index of external.
- */
- public function free($index) {
- unset($this->externals[$index]);
- }
- }
- /**
- * Main class of FastPage library.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- */
- class FastPage extends FastPage_Core {
- /**
- * Constructor.
- */
- public function __construct() {
- parent::__construct();
- // Default configs.
- $config = $this->config();
- // Make JavaScripts inline.
- $config->sub('inline_js')->max_file_size = 1024 * 10;
- // Max file size and files to convert Data URI schema.
- $config->sub('data_uri')->max_file_size = 1024 * 10;
- $config->sub('data_uri')->max_redundancy = 3;
- // Exclude URLs.
- $config->exclude_url = new FastPage_TextFilters;
- $config->exclude_path = new FastPage_TextFilters;
- // Built-in helpers.
- $builtin_helpers = array(
- 'context' => 'Context',
- 'request' => 'Request',
- 'response' => 'Response',
- 'externals' => 'Externals',
- );
- foreach ( $builtin_helpers as $helper => $class ) {
- $this->helper_class( $helper, 'FastPage_' . $class );
- }
- // Built-in body handlers.
- $builtin_handlers = array(
- 'css' => 'css',
- 'html' => 'html',
- 'htm' => 'html'
- );
- // Handlers.
- foreach ( $builtin_handlers as $ext => $type ) {
- $this->add_callback( 'convert_body.' . $ext, 10, array( $this, 'cb_convert_' . $type ) );
- }
- // Other built-in callbacks.
- $builtin_callbacks = array(
- 'original_response',
- 'generate_etag',
- 'convert_externals'
- );
- foreach ( $builtin_callbacks as $cb ) {
- $this->add_callback( $cb, 5, array( $this, 'cb_' . $cb ) );
- }
- // Run callback.
- $this->run_callbacks( 'init_fastpage', FastPage_Callback::all, $this );
- }
- /**
- * Resolve URL to real path.
- *
- * @param string $url A URL to resolve real path.
- * @param string $base_dir A base directory path to resolve relative URL.
- * @param boolean $safety Deny to refer above of document root.
- * @param string $domain Optinal. Domain part of URL.
- * @param string $relative_path Relative path from document root.
- */
- public function url_to_path( $url, $base_dir = null, $safety = true, &$domain = null, &$relative_path = null ) {
- // Strip params.
- $url = FastPage_Util::strip_url_params($url);
- $document_root = $this->document_root();
- $path = null;
- if ( substr( $url, 0, 1 ) == '/' && substr( $url, 1, 1 ) != '/' ) {
- // In the case of absolute path.
- $path = FastPage_Util::cat_path( $document_root, $url );
- } else if ( preg_match( '!^(https?:)?//([^/]+)(/.+)!i', $url, $matches ) ) {
- // In the case of absolute URL.
- $path = array_pop($matches);
- $domain = array_pop($matches);
- $document_root = $this->document_root($domain);
- if ( !$document_root ) return null;
- $path = FastPage_Util::cat_path( $document_root, $path );
- } else if ( isset($base_dir) ) {
- // In the case of relative path.
- $path = FastPage_Util::cat_path( $base_dir, $url );
- }
- $len = strlen($document_root);
- if ( substr( $path, 0, $len ) == $document_root ) {
- $relative_path = substr( $path, $len );
- if ( substr( $relative_path, 0, 1 ) != DIRECTORY_SEPARATOR )
- $relative_path = DIRECTORY_SEPARATOR . $relative_path;
- } else if ( $safety ) {
- // Never allow to refer above of document_root by security reason.
- return null;
- }
- return $path;
- }
- /**
- * Returns a new FastPage_Context instance.
- *
- * @return FastPage_Context A new instance.
- */
- public function new_context() {
- $context = $this->helper_instance( 'context' );
- // Run callbacks.
- $this->run_callbacks( 'new_context', FastPage_Callback::all, $context );
- $this->last_context = $context;
- return $context;
- }
- /**
- * Returns the last context.
- *
- * @return FastPage_Context The instance.
- */
- public function last_context() {
- return $this->last_context;
- }
- /**
- * Generate etag.
- *
- * A proxy to run generate_etag callbacks.
- *
- * @param mixed $context FastPage context object.
- */
- public function generate_etag( $context ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, "Generating etag" );
- $result = $this->run_callbacks( 'generate_etag', FastPage_Callback::first_one, $context );
- FastPage_Debug::log( $this, E_FP_PROFILE, "Generating etag", $result );
- return $result;
- }
- /**
- * Default callback to generate Etag.
- */
- public function cb_generate_etag( $callback, $context, $hint ) {
- $fastpage = $callback->fastpage;
- // Standard etag generator with md5 or sha1.
- $algorithm = $fastpage->config('etag')->algorithm;
- if ( !isset($algorithm) || $algorithm != 'sha1' )
- $algorithm = 'md5';
- // Etag partials.
- $partials = array();
- // Body source.
- if( $context->response->body ) {
- $partials []= $algorithm($context->response->body);
- }
- // Files reffered.
- // Join to "path1:etag1;..pathN:etagN".
- if ( $context->response->externals ) {
- $file_etags = array();
- foreach ( $context->response->externals->externals as $external ) {
- if ( !$external->path ) continue;
- if ( null !== ( $file_etag = FastPage_Util::file_etag($external->path) ) ) {
- $file_etags []= $external->path . ':' . $file_etag;
- }
- }
- if( count($file_etags) ) {
- $joined = join( ';', $file_etags );
- $partials []= $algorithm($joined);
- }
- }
- $etag = join( '-', $partials );
- return $etag;
- }
- /**
- * Get original response.
- *
- * A proxy to run original_response callbacks.
- *
- * @param FastPage_Context $context FastPage request context.
- */
- public function original_response( $context ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Original response' );
- // Default response information.
- $path = $context->request->path;
- if ( $path ) {
- $pi = pathinfo($path);
- $ext = strtolower($pi['extension']);
- $base_dir = $pi['dirname'];
- $content_type = $this->mime_type($ext);
- $context->response->merge_values( array(
- 'path' => $path,
- 'ext' => $ext,
- 'base_dir' => $base_dir,
- 'content_type' => $content_type
- ) );
- }
- if ( $this->run_callbacks( 'original_response', FastPage_Callback::first_one, $context ) ) {
- // Detect response charset.
- $charset = $this->config()->content_charset;
- if ( !$charset || strtolower($charset) == 'auto' ) {
- if ( function_exists('mb_detect_encoding') ) {
- $charset = mb_detect_encoding( $context->response->body );
- } else {
- $charset = 'UTF-8';
- }
- }
- $context->response->charset = $charset;
- // Normalize to UTF-8.
- if ( strtoupper($charset) != 'UTF-8' && function_exists('mb_convert_encoding') ) {
- $context->response->body = mb_convert_encoding( $context->response->body, 'UTF-8', $charset );
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Original response' );
- return true;
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Original response' );
- return null;
- }
- /**
- * Default 'original_response' callback handler.
- *
- * @param FastPage_Context $context Request context.
- */
- public function cb_original_response( $callback, $context ) {
- if ( !$path = $context->response->path ) return null;
- // Expand the body source.
- $content_type = $context->response->content_type;
- if ( $content_type == 'text/html' || $content_type == 'text/css' ) {
- $body = file_get_contents($path);
- // Set response properties.
- $context->response->merge_values( array(
- 'body' => &$body,
- ) );
- return true;
- }
- return null;
- }
- /**
- * Convert external file reference.
- *
- * A proxy to run generate_etag callbacks.
- *
- * @param mixed $context FastPage context object.
- */
- public function convert_externals( $context ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Convert externals' );
- $result = $this->run_callbacks( 'convert_externals', FastPage_Callback::all, $context );
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Convert externals' );
- return $result;
- }
- /**
- * Convert externals.
- *
- * @param FastPage_Externals $externals External file references.
- */
- public function cb_convert_externals( $callback, $context, $hint ) {
- $fastpage = $callback->fastpage;
- $externals = $context->response->externals;
- if ( !$externals ) return;
- // Config.
- $config = $this->config();
- $data_uri_config = $fastpage->config('data_uri');
- $inline_js_config = $fastpage->config('inline_js');
- $cache = $fastpage->cache();
- foreach ( $externals->externals as $index => $external ) {
- if ( $external->is_image() ) {
- // Convert image to Data_URI.
- if ( !$context->user_agent->data_uri_enabled ) continue;
- // Skip duplicated image.
- if ( $data_uri_config->max_redundancy && $external->redundancy && $external->redundancy > $data_uri_config->max_redundancy ) {
- continue;
- }
- // Check file size.
- $size = filesize($external->path);
- if ( !$size ) continue;
- if ( $data_uri_config->max_file_size && $size > $data_uri_config->max_file_size ) {
- continue;
- }
- // Replace to data uri.
- if ( $data_uri = $external->data_uri() ) {
- $external->replace_type = 'url';
- $external->replace = $data_uri;
- }
- } else if ( $external->is_js() ) {
- // Convert JavaScript.
- // Check file size.
- $size = filesize($external->path);
- if ( !$size ) continue;
- if ( $size && $size > $inline_js_config->max_file_size ) {
- continue;
- }
- // Make JavaScript inline.
- if ( false !== ( $contents = file_get_contents($external->path) ) ) {
- $external->replace_type = 'inline';
- $external->replace = $contents;
- }
- }
- }
- }
- /**
- * A proxy to run convert_* callbacks.
- */
- public function convert_body( $context ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Convert body' );
- // Extension.
- if ( !($ext = $context->response->ext ) ) {
- return null;
- }
- $event = 'convert_body.' . $ext;
- if ( $this->run_callbacks( $event, FastPage_Callback::first_one, $context ) ) {
- // Convert encoding.
- if ( function_exists('mb_convert_encoding') ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Convert encoding' );
- $charset = isset($context->charset)? $context->charset: $context->response->charset;
- if ( strtoupper($charset) != 'UTF-8' ) {
- $context->response->body = mb_convert_encoding( $context->response->body, $charset, 'UTF-8' );
- $context->response->charset = $charset;
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Convert encoding' );
- }
- $context->response->content_length = strlen($context->response->body);
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Convert body' );
- }
- /**
- * Default callback to convert CSS body.
- */
- public function cb_convert_css( $callback, $context, $hint ) {
- $fastpage = $callback->fastpage;
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Parse CSS source' );
- // Tokenize.
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Tokenize' );
- $tokens = preg_split( '/(url\([^\)]+\))/i', $context->response->body, null, PREG_SPLIT_DELIM_CAPTURE );
- $tokens_count = count($tokens);
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Tokenize' );
- // Enum urls.
- $urls = array();
- for( $i = 1; $i < $tokens_count; $i += 2 ) {
- $url = substr( $tokens[$i], 4, -1 ); // Strip url(...)
- $url = FastPage_Util::strip_quotes($url); // Strip quotes.
- $urls[$i] = $url;
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Parse CSS source' );
- // Make external references collection.
- $context->response->externals = $fastpage->helper_instance( 'externals', false, $context, $urls );
- // Etag matching.
- if ( $context->match_etag() ) return true;
- // Replace tokens.
- $fastpage->convert_externals($context);
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Rebuild CSS source' );
- $externals = $context->response->externals;
- foreach ( $externals->externals as $index => $external ) {
- if ( $external->replace_type == 'url' && $external->replace ) {
- $tokens[$index] = 'url("' . $external->replace . '")';
- }
- // Free objects.
- unset($external);
- $externals->free($index);
- }
- // Build body.
- $context->response->body = join( '', $tokens );
- unset($tokens);
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Rebuild CSS source' );
- return true;
- }
- /**
- * Default callback to convert HTML body.
- */
- public function cb_convert_html( $callback, $context, $hint ) {
- $fastpage = $callback->fastpage;
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Parse HTML source' );
- // Tokenize.
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Tokenize' );
- $tokens = preg_split('!(</?(?:img|script)\s?[^>]*>)!i', $context->response->body, null, PREG_SPLIT_DELIM_CAPTURE);
- $tokens_count = count($tokens);
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Tokenize' );
- // Enum URLs.
- $elements = array();
- $urls = array();
- for ( $i = 0; $i < $tokens_count; $i++ ) {
- if ( substr( $tokens[$i], 0, 4 ) == '<img' || substr( $tokens[$i], 0, 7 ) == '<script' ) {
- $element = $fastpage->parse_html_element($tokens[$i]);
- if ( !isset($element) ) continue;
- // Src attribute
- $src = $element->attribute('src');
- if ( !isset($src) ) continue;
- $elements[$i] = $element;
- $urls[$i] = $src;
- }
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Parse HTML source' );
- // Make external references collections.
- $context->response->externals = $fastpage->helper_instance( 'externals', false, $context, $urls );
- // Etag matching.
- if ( $context->match_etag() ) return true;
- // Replace tokens.
- $fastpage->convert_externals($context);
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Rebuild HTML source' );
- $externals = $context->response->externals;
- foreach ( $externals->externals as $index => $external ) {
- // Need to replace?
- if ( !$external->replace || !$external->replace_type ) continue;
- // HTML element.
- $element = $elements[$index];
- if ( !$element ) continue;
- if ( $element->tag_name == 'img' ) {
- // IMG element.
- if ( $external->replace_type == 'url' ) {
- // Replace src attribute to converted.
- $element->attribute( 'src', $external->replace );
- $tokens[$index] = $element->html;
- }
- } else if ( $element->tag_name == 'script' ) {
- // SCRIPT element.
- if ( $external->replace_type == 'inline' ) {
- // Make script inline.
- $element->remove_attribute('src');
- $tokens[$index] = $element->html . "\n" .$external->replace . "\n";
- } else if ( $external->replace_type == 'url') {
- // Replace src attribute to converted.
- $element->attribute( 'src', $external->replace );
- $tokens[$index] = $element->html;
- }
- }
- // Free objects.
- unset($external);
- $externals->free($index);
- unset($element);
- unset($elements[$index]);
- }
- // Build body.
- $context->response->body = join( '', $tokens );
- unset($tokens);
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Rebuild HTML source' );
- return true;
- }
- }
- /**
- * FastPage http exception.
- */
- class FastPage_HTTPException extends FastPage_Exception {
- public $fastpage;
- public $code;
- public $level;
- public $message;
- /**
- * HTTP response code prefix.
- */
- public static $response_code_prefix = 'HTTP/1.1';
- /**
- * HTTP response code table.
- */
- public static $response_codes = array(
- 200 => 'OK',
- 304 => 'Not Modified',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 501 => 'Not Implemented',
- 505 => 'Internal Server Error',
- );
- /**
- * Constructor.
- *
- * Log message and keep http response code.
- *
- * @param object $fastpage FastPage instance.
- * @param mixed $code HTTP response code as integer or CODE MESSAGE as string.
- * @param integer $level Log level.
- * @param string $message Log message.
- */
- public function __construct( $fastpage, $code, $level = 0, $message = '') {
- $this->fastpage = $fastpage;
- $this->code = $code;
- $this->level = $level;
- $this->message = $message;
- // Add log.
- if ( $message ) {
- FastPage_Debug::log( $fastpage, $level, $message );
- }
- }
- /**
- * Send HTTP response code.
- */
- public function response_code() {
- // Output response code header.
- $prefix = self::$response_code_prefix;
- $code = $this->code;
- $error = isset(self::$response_codes[$code])? self::$response_codes[$code]: '';
- if ( isset($error) ) {
- return "$prefix $code $error";
- } else {
- return "$prefix $code";
- }
- }
- }
- /**
- * FastPage application wrapper.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- */
- class FastPage_App extends FastPage {
- protected static $instance;
- public function __construct() {
- parent::__construct();
- // Default config.
- $config = $this->config();
- $config->default_host = FastPage_Util::server('HTTP_HOST');
- $config->default_document_root = FastPage_Util::server('DOCUMENT_ROOT');
- $config->directory_index = array('index.html');
- $config->sub('tasks')->regular_probability = 1000;
- // Built-in callbacks.
- $this->add_callback( 'regular_tasks', 5, array( $this, 'cb_regular_tasks' ) );
- }
- /**
- * Callback handler of default regular_tasks.
- */
- public function cb_regular_tasks( $callback ) {
- $fastpage = $callback->fastpage;
- // Purge cache.
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Purge cache' );
- $cache = $fastpage->cache();
- $cache->purge();
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Purge cache' );
- return true;
- }
- /**
- * Get singleton instance.
- *
- * @return FastPage_App Singleton instance.
- */
- public function instance() {
- return self::$instance;
- }
- /**
- * Prepare request.
- *
- * Must be overridden.
- *
- * @param FastPage_Context $context Context object.
- */
- public function prepare_request( $context ) { }
- /*
- * Output headers.
- */
- public function output_headers( $response_code, $context ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Output Headers' );
- // Run callback.
- $this->run_callbacks( 'output_headers', FastPage_Callback::all, $context );
- // Response code.
- if ( $response_code ) {
- header( $response_code );
- }
- // Output headers.
- $headers = $context->response->build_headers();
- $headers['X-FastPage'] = FASTPAGE_VERSION;
- foreach ( $headers as $name => $value ) {
- $value = preg_replace( '/[\n\r]+/', ' ', $value );
- header("$name: $value");
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Output Headers' );
- }
- /**
- * Output body.
- */
- public function output_body( $context ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Output Body' );
- // Run callback.
- $this->run_callbacks( 'output_body', FastPage_Callback::all, $context );
- if ( $context->response->body ) {
- ob_start();
- echo $context->response->body;
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Output Body' );
- }
- /*
- * Run process.
- */
- public function run() {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Run app' );
- // Create a new context and start process.
- $context = $this->new_context();
- // Server cache frequency.
- $frequency = $this->config('server_cache')->frequency;
- $method_is_get = $context->request->method == 'GET'? true: false;
- try {
- // Prepare request.
- $this->prepare_request($context);
- if ( !$context->request->url ) {
- throw new FastPage_HTTPException( $this, 404, E_USER_ERROR, 'Cannot get target url.' );
- }
- // Etag.
- if ( $context->requires_etag && $etag = FastPage_Util::server('HTTP_IF_NONE_MATCH') ) {
- $context->request->etag = FastPage_Util::strip_quotes($etag);
- }
- // Check server cache.
- $hit_server_cache = false;
- $cache = $this->cache();
- if ( $frequency && $method_is_get ) {
- // Make cache keys array.
- $context->request->server_cache_keys = array(
- 'URL' => $context->request->url,
- '_GET' => &$_GET,
- '_COOKIE' => &$_COOKIE,
- '_SESSION' => &$_SESSION
- );
- $this->run_callbacks( 'server_cache_keys', FastPage_Callback::all, $context );
- $key = $context->request->server_cache_key = md5( serialize($context->request->server_cache_keys) );
- // Search cache key.
- if ( $cached = $cache->get( 'server_cache', $key ) ) {
- // Cache enabled and cached context exists.
- $response = @unserialize($cached);
- if ( $response ) {
- FastPage_Debug::log( $this, E_USER_NOTICE, 'URL cache hit.' );
- $context->response = $response;
- $hit_server_cache = true;
- }
- }
- }
- if ( !$hit_server_cache ) {
- // Start normal flow.
- // Get original response.
- $result = $this->original_response($context);
- if ( !$result ) {
- // Can't get original response.
- throw new FastPage_HTTPException( $this, 404, E_USER_ERROR, 'Cannot get original response.' );
- }
- // Pass through?
- if ( $context->pass_through ) {
- throw new FastPage_HTTPException( $this, 200, E_USER_NOTICE, 'Pass through the request.' );
- }
- // Convert the source.
- $result = $this->convert_body($context);
- }
- // Not modified?
- if ( $context->match_etag() ) {
- // Not modified, clear body not to send.
- $context->response->body = '';
- throw new FastPage_HTTPException( $this, 304, E_USER_NOTICE, 'Not modified.' );
- }
- // Output body.
- // FIXME: This may not be smart...
- throw new FastPage_HTTPException( $this, 200 );
- } catch ( FastPage_HTTPException $ex ) {
- // Output HTTP response.
- $response_code = $ex->response_code();
- // Cache the result.
- if ( $frequency && $method_is_get && !$hit_server_cache && $response_code == 200 ) {
- $now = time();
- $expire = (int)( ceil( $now / $frequency ) * $frequency ) - $now;
- $caching = serialize($context->response);
- $cache->set( 'server_cache', $context->request->server_cache_key, $caching, $expire );
- FastPage_Debug::log( $this, E_USER_NOTICE, 'Server cache stored. It will expire after ' . $expire . ' seconds.' );
- }
- $this->output_headers( $response_code, $context );
- $this->output_body( $context );
- }
- // $this->add_onetime_task( 'dummy_task', array( $this, 'dummy_task' ) );
- // Run background tasks.
- $this->run_background_tasks();
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Run app' );
- }
- /**
- * Run background tasks.
- */
- public function run_background_tasks() {
- // Onetime tasks.
- if ( $this->has_onetime_tasks() ) {
- // Fork tasks.
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Fork onetime tasks' );
- $key = $this->save_onetime_tasks();
- if ( !$this->fork_tasks( array( 'onetime_tasks' => $key ) ) ) {
- // Failed to fork, run at here.
- FastPage_Debug::log( $this, E_USER_WARNING, 'Forking onetime tasks failed.' );
- $this->run_onetime_tasks();
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Fork onetime tasks' );
- }
- // Regular tasks.
- if ( $prob = $this->config('tasks')->regular_probability ) {
- // If random value match 1, fork regular tasks.
- srand( (int)microtime(true) );
- if ( 1 == rand( 1, $prob ) ) {
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Fork regular tasks' );
- if ( !$this->fork_tasks() ) {
- // Failed to fork, run at here.
- FastPage_Debug::log( $this, E_USER_WARNING, 'Forking regular tasks failed.' );
- $this->run_regular_tasks();
- }
- FastPage_Debug::log( $this, E_FP_PROFILE, 'Fork regular tasks' );
- }
- }
- }
- /**
- * Forking URL.
- *
- * @param string $script Script name.
- * @param array $param URL parameters as array.
- * @param string $param_prefix Prefix to pass http_build_query.
- */
- public function fork_url( $script, $param = array(), $param_prefix = '' ) {
- // Get base from config.
- $url_base = $this->config()->fork_url_base;
- // Detect URL if not defined.
- if ( !$url_base ) {
- $name = FastPage_Util::server('SCRIPT_NAME');
- $host = FastPage_Util::server('HTTP_HOST');
- $script_uri = "http://$host$name";
- $url_base = preg_replace( '!/[^/]*$!', '/', $script_uri );
- }
- // Concat script.
- $url = FastPage_Util::cat_path( $url_base, $script );
- // Add param.
- if ( count($param) ) {
- $url .= '?' . http_build_query( $param, $param_prefix, '&' );
- }
- return $url;
- }
- /**
- * Forking tasks via HTTP getting task.php.
- *
- * @param array $param Parameters to pass task.php.
- * @return boolean False if failed forking.
- */
- public function fork_tasks( $param = array() ) {
- $result = true;
- // Build url.
- if ( $debug = FastPage_Debug::instance() ) {
- $param['start'] = $debug->start;
- }
- $url = $this->fork_url( 'task.php', $param );
- // Fork tasks via HTTP.
- if ( $url ) {
- $http = $this->simple_http();
- $response = $http->get($url);
- if ( !isset($response) ) {
- // Failed to get.
- $result = false;
- } else if ( $response != 'OK' ) {
- $result = @unserialize($response);
- // Append logs.
- if ( is_array($result) ) {
- // Merge debug logs.
- if ( $debug = FastPage_Debug::instance() ) {
- if ( isset($result['debug']) && is_a( $result['debug'], 'FastPage_Debug' ) ) {
- $debug->merge( $result['debug'] );
- }
- }
- } else {
- // Some error occured, print raw response.
- echo $response;
- }
- }
- } else {
- FastPage_Debug::log( $this, E_USER_ERROR, 'Failure to get task forking URL.' );
- $result = false;
- }
- return $result;
- }
- /**
- * Bootstrap of FastPage_App.
- *
- * @param string $class The child class name of FastPage_App.
- * @param mixed $config_path Optionsl. Config file path to load. Auto detection by default. Set false if no need to load config file.
- * @return FastPage_Rewrite
- */
- static public function boot( $class, $config_path = null ) {
- FastPage_Debug::log( null, E_FP_PROFILE, 'Bootstrap' );
- // Create an instance.
- if ( !class_exists($class) ) {
- FastPage_Debug::log( null, E_USER_ERROR, 'Class: ' . $class . ' does not found.' );
- header( 'HTTP/1.1 501 Not Implemented: ' . $class );
- return;
- }
- FastPage_Debug::log( null, E_USER_NOTICE, 'Boot up: ' . $class );
- $fastpage = self::$instance = new $class();
- // Load config file.
- $fastpage->load_config_file( $config_path );
- // Check IP addres if be debuggable.
- if ( $ips = $fastpage->config('debug')->allow_ips ) {
- $remote = FastPage_Util::server('REMOTE_ADDR');
- if ( !FastPage_Util::match_ips( $ips, $remote, true ) ) {
- // Cancel debugging.
- FastPage_Debug::$instance = null;
- }
- }
- // Run the instance.
- // * Good to loop this in service type process.
- $fastpage->run();
- FastPage_Debug::log( $fastpage, E_FP_PROFILE, 'Bootstrap' );
- $fastpage->run_callbacks( 'app_finished', FastPage_Callback::all );
- return;
- }
- }
- /**
- * Rewite application for mod_rewite.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga <miyanaga@ideamans.com>
- * @var mixed request_url_var Server variable name to get URL.
- */
- class FastPage_App_Rewrite extends FastPage_App {
- /**
- * Override prepare_request method.
- */
- public function prepare_request( $context ) {
- // Host and method.
- $context->request->method = FastPage_Util::server('REQUEST_METHOD');
- $context->request->host = FastPage_Util::server('HTTP_HOST');
- // Target URL.
- $url = FastPage_Util::strip_url_params( FastPage_Util::server('REQUEST_URI') );
- $path = $this->url_to_path( $url );
- if ( is_dir($path) ) {
- // Search directory index.
- foreach ( $this->config()->directory_index as $index ) {
- $index_path = FastPage_Util::cat_path( $path, $index );
- if ( is_file($index_path) ) {
- $url = FastPage_Util::cat_path( $url, $index );
- $path = $index_path;
- break;
- }
- }
- }
- if ( !is_file($path) ) {
- // Not found.
- return;
- }
- $context->request->url = $url;
- $context->request->path = $path;
- }
- }
- /**
- * Task application.
- *
- * @package FastPage
- * @author Kunihiko Miyanaga
- */
- class FastPage_App_Task extends FastPage_App {
- /**
- * Error handler to redirect to FastPage_Debug.
- */
- public function error_handler( $errno, $errstr, $errfile, $errline ) {
- $message = $errstr;
- if ( $errfile && $errline ) {
- $message .= "($errfile:$errline)";
- }
- FastPage_Debug::log( $this, $errno, $message );
- return true;
- }
- /**
- * Run background tasks.
- */
- public function run() {
- // Set error handler to redirect to FastPage_Debug.
- set_error_handler( array( $this, 'error_handler' ) );
- $denied = false;
- // Check IP.
- if ( $ips = $this->config('tasks')->allow_ips ) {
- $remote = FastPage_Util::server('REMOTE_ADDR');
- if ( !FastPage_Util::match_ips( $ips, $remote, true ) ) {
- $denied = true;
- }
- }
- // Exit if denied.
- if ( $denied ) {
- header('HTTP/1.1 403 Forbidden');
- return;
- }
- // If not in debug mode, try to close client connection immediately.
- if ( !FastPage_Debug::instance() ) {
- $response = 'OK';
- ignore_user_abort(true);
- header('Content-Type: application/x-fastpage');
- header('Connection: close');
- header('Content-Length: ' . strlen($response));
- echo $response;
- while( @ob_end_flush() );
- flush();
- }
- // Run tasks.
- ob_start();
- if ( isset($_GET['onetime_tasks']) ) {
- // Restore and run tasks.
- $key = $_GET['onetime_tasks'];
- $this->load_onetime_tasks($key);
- $this->run_onetime_tasks();
- } else {
- // Run regular tasks.
- $this->run_regular_tasks();
- }
- ob_end_clean();
- // If debug mode, output serialized debug object.
- if ( $debug = FastPage_Debug::instance() ) {
- $response = serialize( array( 'debug' => $debug ) );
- echo $response;
- }
- }
- }