url = $url; $this->consumer_secret = $consumer_secret; $this->access_token_secret = $access_token_secret; $this->signature_method = $signature_method; $oauth = array(); $oauth['oauth_consumer_key'] = $consumer_key; $oauth['oauth_token'] = $access_token; $oauth['oauth_nonce'] = md5(uniqid(rand(), TRUE)); $oauth['oauth_timestamp'] = time(); $oauth['oauth_signature_method'] = $signature_method; if (isset($params['oauth_verifier'])) { $oauth['oauth_verifier'] = $params['oauth_verifier']; unset($params['oauth_verifier']); } $oauth['oauth_version'] = '1.0'; // encode all oauth values foreach($oauth as $k => $v) $oauth[$k] = self::encode_rfc3986($v); $sig_params = array(); $has_file = FALSE; foreach(((array) $params) as $k => $v) { if (strncmp('@', $k, 1) !== 0) { $sig_params[$k] = self::encode_rfc3986($v); $params[$k] = self::encode_rfc3986($v); } else { $params[substr($k, 1)] = $v; unset($params[$k]); $has_file = TRUE; } } if ($has_file === TRUE) $sig_params = array(); $sig_params = array_merge($oauth, (array) $sig_params); // sort ksort($sig_params); // sign $param_string = self::encode_rfc3986(self::build_http_query_raw($sig_params)); $normalized_url = self::encode_rfc3986(self::normalize_url($url)); $method = self::encode_rfc3986($method); $signature_base_string = "{$method}&{$normalized_url}&{$param_string}"; $oauth['oauth_signature'] = self::encode_rfc3986($this->sign_string($signature_base_string)); $this->header_content = $oauth; } function getHeader() { $url_parts = parse_url($this->url); $header = 'OAuth realm="' . $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '",'; foreach($this->header_content as $name => $value) { $header .= "{$name}=\"{$value}\","; } // trim off the last comma $header = substr($header, 0, -1); clAPI::debug("oAuth Authorization Header: ".$header); return $header; } private function sign_string($string) { $retval = FALSE; switch($this->signature_method) { case 'HMAC-SHA1': $key = self::encode_rfc3986($this->consumer_secret) . '&' . self::encode_rfc3986($this->access_token_secret); $retval = base64_encode(hash_hmac('sha1', $string, $key, TRUE)); break; default: throw new Exception("coreylib does not support signing oAuth requests with signature method {$this->signature_method}"); break; } return $retval; } static function encode_rfc3986($string) { return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode(($string)))); } static function build_http_query_raw($params) { $retval = ''; foreach((array) $params as $key => $value) $retval .= "{$key}={$value}&"; $retval = substr($retval, 0, -1); return $retval; } static function normalize_url($url = null) { $url_parts = parse_url($url); $scheme = strtolower($url_parts['scheme']); $host = strtolower($url_parts['host']); $port = isset($url_parts['port']) ? intval($url_parts['port']) : 0; $retval = strtolower($scheme) . '://' . strtolower($host); if (!empty($port) && (($scheme === 'http' && $port != 80) || ($scheme === 'https' && $port != 443))) $retval .= ":{$port}"; $retval .= $url_parts['path']; if (!empty($url_parts['query'])) $retval .= "?{$url_parts['query']}"; return $retval; } } /** * Universal AWS ECS parser. * @since 1.0.8 */ class clAWSECS extends clAPI { private $aws_secret_key; private $aws_access_key_id; private $aws_service; private $aws_associate_tag; /** * @param String associate_tag The Amazon associate tag to associate with this request * @param String access_key_id Your public AWS access key * @param String secret_key Your private AWS secret key */ function __construct($associate_tag, $access_key_id, $secret_key, $service='AWSECommerceService') { $this->aws_associate_tag = $associate_tag; $this->aws_access_key_id = $access_key_id; $this->aws_secret_key = $secret_key; $this->aws_service = $service; parent::__construct('http://ecs.amazonaws.com/onca/xml'); } function parse($cacheFor = 0) { $this->signAWSECS(); return parent::parse($cacheFor); } /** * Properly sign an AWS ECS request. Based on signature implementation by Blake Schwendiman. * @see http://www.thewhyandthehow.com/signing-aws-requests-in-php/ */ private function signAWSECS() { $url_parts = split('://', $this->url); $host_and_path = split('/', $url_parts[1]); $host = array_shift($host_and_path); $path = '/'.join('/', $host_and_path); // parameter defaults: $params = array_merge(array( 'Operation' => 'ItemSearch', 'Service' => $this->aws_service, 'Version' => '2009-06-01', 'AWSAccessKeyId' => $this->aws_access_key_id, 'AssociateTag' => $this->aws_associate_tag ), $this->params); if ($params['Operation'] == 'ItemSearch' && !isset($params['SearchIndex'])) $params['SearchIndex'] = 'Blended'; // next, override timestamp: $params = array_merge($params, array( 'Timestamp' => gmdate('Y-m-d\TH:i:s\Z') )); // do a case-insensitive, natural order sort on the array keys. ksort($params); // create the signable string $temp = array(); foreach ($params as $k => $v) { $temp[] = str_replace('%7E', '~', rawurlencode($k)) . '=' . str_replace('%7E', '~', rawurlencode($v)); } $signable = join('&', $temp); $stringToSign = strtoupper($this->method)."\n$host\n$path\n$signable"; $this->debug($stringToSign); // Hash the AWS secret key and generate a signature for the request. $hex_str = hash_hmac('sha256', $stringToSign, $this->aws_secret_key); $raw = ''; for ($i = 0; $i < strlen($hex_str); $i += 2) { $raw .= chr(hexdec(substr($hex_str, $i, 2))); } $params['Signature'] = base64_encode($raw); ksort($params); $this->params = $params; } } /** * Universal web service parser. * @package coreylib * @since 1.0.0 */ class clAPI { const METHOD_GET = 'get'; const METHOD_POST = 'post'; protected $params = array(); protected $username; protected $password; protected $consumserKey; protected $consumerSecret; protected $accessToken; protected $accessTokenSecret; protected $headers; protected $content; protected $parserType; protected $parsed; protected $url; protected $sxml; protected $tree; protected $method = self::METHOD_GET; protected $ch; protected $multi_mode = false; protected $cacheName; protected $cacheFor; protected $curlopts = array(); public static $options = array( "display_errors" => false, "debug" => false, "nocache" => false, "max_download_tries" => 3, "trace" => false ); function __construct($url, $parserType = COREYLIB_PARSER_XML) { $this->header('Expect', ''); if (clAPI::$options['debug'] || clAPI::$options['display_errors']) { error_reporting(E_ALL); ini_set('display_errors', true); } if (!empty($url)) { $this->url = $url; if (!$parserType) { // attempt autodetection if (preg_match('/(xml|rss)$/', $url)) $parserType = COREYLIB_PARSER_XML; else if (preg_match('/(json)$/', $url)) $parserType = COREYLIB_PARSER_JSON; else self::error("Please specify a parser type for $url - parameter two of the constructor should be one of COREYLIB_PARSER_XML or COREYLIB_PARSER_JSON."); } $this->parserType = $parserType; } else self::error("Um... you have to tell me what URL you want to parse."); } function __toString() { return ($this->content ? $this->content : ''); } static function configure($option_name_or_array, $value_or_null = null) { if (is_array($option_name_or_array)) self::$options = array_merge(self::$options, $option_name_or_array); else self::$options[$option_name_or_array] = $value_or_null; } static function error($msg) { if (clAPI::$options['debug'] || clAPI::$options['display_errors']) { ?>
username = $username; $this->password = $password; return $this; } /** * @since 1.1.5 */ function oAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret) { $this->consumerKey = $consumerKey; $this->consumerSecret = $consumerSecret; $this->accessToken = $accessToken; $this->accessTokenSecret = $accessTokenSecret; return $this; } /** * @since 1.1.5 */ function header($name, $value) { $this->headers[$name] = $value; return $this; } private function queryString() { $qs = array(); foreach($this->params as $name => $value) $qs[] = $name."=".urlencode($value); return join('&', $qs); } /** * @since 1.0.10 */ public final function curlopt($constant, $value) { $this->curlopts[$constant] = $value; return $this; } private function download($url) { self::debug(($this->multi_mode ? 'Queueing' : 'Downloading')." $this->url"); $qs = $this->queryString(); $url = ($this->method == self::METHOD_GET ? $this->url.($qs ? '?'.$qs : '') : $this->url); $this->ch = curl_init($url); curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->ch, CURLOPT_USERAGENT, 'coreylib'); // accept all SSL certificates: curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false); if ($this->username && $this->password) { curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($this->ch, CURLOPT_USERPWD, "$this->username:$this->password"); } if ($this->consumerKey && $this->consumerSecret && $this->accessToken && $this->accessTokenSecret) { $oauth = new oAuthSupport( $this->url, $this->consumerKey, $this->consumerSecret, $this->accessToken, $this->accessTokenSecret, $this->params, strtoupper($this->method) ); $this->header('Authorization', $oauth->getHeader()); } // set headers $headers = array(); foreach($this->headers as $name => $value) { $headers[] = "{$name}: {$value}"; } curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers); if ($this->method == self::METHOD_POST) { curl_setopt($this->ch, CURLOPT_POST, true); curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->params); } else curl_setopt($this->ch, CURLOPT_HTTPGET, true); foreach($this->curlopts as $const => $value) { if ($const == CURLOPT_RETURNTRANSFER && $value != true) { throw new Exception("Can't set CURL option CURLOPT_RETURNTRANSFER to false; that would break coreylib!"); } curl_setopt($this->ch, $const, $value); } // in multi mode, we return the curl handle reference; otherwise, we execute and return content return ($this->multi_mode ? $this->ch : curl_exec($this->ch)); } /** * @since 1.0.6 */ function post($cacheFor = 0) { $this->method = self::METHOD_POST; return $this->parse($cacheFor); } function parse($cacheFor = 0) { $this->content = false; $this->cacheFor = $cacheFor; $qs = $this->queryString(); $url = $this->url.($qs ? '?'.$qs : ''); $this->cacheName = $url.md5($this->username.$this->password).md5($this->consumerKey.$this->accessToken); $contentCameFromCache = false; if ($this->cacheFor && !clAPI::$options['nocache']) $this->content = clCache::cached($this->cacheName, $this->cacheFor, true); if ($this->content === false) { if (!$this->multi_mode) { $try = 0; do { $try++; $this->content = $this->download($url); } while (empty($this->content) && $try < clAPI::$options['max_download_tries']); } else { // in multi mode, the curl handle is returned from download, not content return $this->download($url); } } else { // in multi mode, when content is cached, we return it if ($this->multi_mode) return $this->content; else $contentCameFromCache = true; } return $this->parseText($this->content, $contentCameFromCache); } /** * @since 1.0.9 */ function parseText($content, $contentCameFromCache = false) { $this->content = $content; if (!$this->content) { if ($this->ch) { $message = curl_error($this->ch); self::error("Failed to download $this->url
$message"); return false; } else { self::error("Content queued from $this->url was null or empty."); return false; } } if ($this->parserType == COREYLIB_PARSER_XML) { if (!($this->sxml = simplexml_load_string($this->content, 'SimpleXMLElement', LIBXML_NOCDATA))) { self::error("Failed to parse content."); return false; } //$this->sxml['xmlns'] = ''; $default_ns = null; if (count(array_keys($namespaces = $this->sxml->getNamespaces(true)))) { // capture the default namespace $default_ns = isset($namespaces['']) ? $namespaces[''] : null; } if ($default_ns) { $this->tree = new clNode($this->sxml, $namespaces, $this->sxml->getName(), $default_ns); } else { $this->tree = new clNode($this->sxml); } } if ($this->cacheFor && !$contentCameFromCache && !clAPI::$options['nocache']) clCache::saveContent($this->cacheName, $this->content, $this->cacheFor); return true; } /** * @since 1.0.9 */ function method($method) { if ($method != clAPI::METHOD_POST && $method != clAPI::METHOD_GET) throw new Exception("Unrecognized HTTP method: $method. Must be one clAPI::METHOD_POST and clAPI::METHOD_GET."); return $this; } /** * Sometimes there will be multi uses of coreylib in a single request. In these cases * it may be helpful to use multi_curl to execute all HTTP requests in parallel. Calling * this method returns one of two values: the content, when content was in the cache, or * a curl handle reference in all other cases. * @return false or a curl handle - see description. * @since 1.0.9 */ function queue($cacheFor = 0) { $this->multi_mode = true; return $this->parse($cacheFor); } /** * @since 1.0.9 */ function get_ch() { return $this->ch; } /** * @deprecated Use clAPI->flushCache instead. */ function flush() { $this->flushCache(); } /** * @since 1.0.9 */ function flushCache() { $qs = $this->queryString(); $url = $this->url.($qs ? '?'.$qs : ''); $cacheName = $url.md5($this->username.$this->password); clCache::flush($cacheName); } function get($path = null, $limit = null, $forgive = false) { if ($this->tree === null) self::error("Can't extract $path until you parse."); else return $this->tree->get($path, $limit, $forgive); } /** * @since 1.1.0 */ function xpath($xpath, $limit = null) { if ($this->tree === null) self::error("Can't extra $path until you parse."); else return $this->tree->xpath($xpath, $limit); } function first($xpath) { return $this->xpath($xpath, 1); } function info($path = null, $limit = null) { if ($this->tree === null) self::error("Can't get info until you parse."); else { return $this->tree->info($path, $limit, $this->url); } } function has($path = null, $atLeast = 1) { if ($this->tree === null) self::error("Can't look for $path until you parse."); else return $this->tree->has($path, $atLeast); } function param($name_or_array, $value = null) { if (is_array($name_or_array)) $this->params = $name_or_array; else $this->params[$name_or_array] = $value; return $this; } function clearParams() { $this->params = array(); } } /** * An intelligent wrapper for SimpleXMLElement objects: forget about namespaces, focus on data. * @package coreylib * @since 1.0.0 */ class clNode implements Iterator { private static $jqueryOut = false; public $__name; public $__value; public $__children = array(); public $__attributes = array(); public $__default_ns = null; public $__default_prefix = null; private $__position; function __construct(SimpleXMLElement $node = null, $namespaces = null, $default_prefix = null, $default_ns = null) { if ($node !== null) { $this->__position = 0; $this->__name = $node->getName(); $this->__value = $node; $this->__default_ns = $default_ns; $this->__default_prefix = $default_prefix; if ($namespaces === null) $namespaces = $node->getNamespaces(true); if ($namespaces === null) $namespaces = array(); if (count($namespaces)) { foreach($namespaces as $ns => $uri) { clAPI::trace("Namespace $ns => $uri"); foreach($node->children(($ns && $uri ? $uri : null)) as $child) { $childName = ($ns ? "$ns:".$child->getName() : $child->getName()); clAPI::trace("Child $childName"); if (array_key_exists($childName, $this->__children) && is_array($this->__children[$childName])) { $this->__children[$childName][] = new clNode($child, $namespaces, $default_prefix, $default_ns); } else if (array_key_exists($childName, $this->__children) && get_class($this->__children[$childName]) == "clNode") { $childArray = array(); $childArray[] = $this->__children[$childName]; $childArray[] = new clNode($child, $namespaces); $this->__children[$childName] = $childArray; } else $this->__children[$childName] = new clNode($child, $namespaces, $default_prefix, $default_ns); } foreach($node->attributes(($ns ? $ns : null)) as $a) { $a = $a->asXML(); @list($name, $value) = split('=', $a); $this->__attributes[trim($name)] = substr($value, 1, strlen($value)-2); } } } else { foreach($node->children() as $child) { $childName = $child->getName(); clAPI::trace("Child $childName"); if (array_key_exists($childName, $this->__children) && is_array($this->__children[$childName])) { $this->__children[$childName][] = new clNode($child, $namespaces, $default_prefix, $default_ns); } else if (array_key_exists($childName, $this->__children) && get_class($this->__children[$childName]) == "clNode") { $childArray = array(); $childArray[] = $this->__children[$childName]; $childArray[] = new clNode($child, $namespaces); $this->__children[$childName] = $childArray; } else $this->__children[$childName] = new clNode($child, $namespaces, $default_prefix, $default_ns); } foreach($node->attributes() as $a) { $a = $a->asXML(); @list($name, $value) = split('=', $a); $this->__attributes[trim($name)] = substr($value, 1, strlen($value)-2); } } } } function has($path = null, $atLeast = 1) { $node = $this->get($path, null, true); if (is_array($node)) return (count($node) >= $atLeast); else if (is_object($node) && $node->__value != null) return (1 >= $atLeast); else if (strlen($node) > 0) return (1 >= $atLeast); else return (0 >= $atLeast); } /** * @since 1.1.0 */ function xpath($xpath, $limit = null) { if ($this->__default_ns && $this->__default_prefix) { $this->__value->registerXPathNamespace($this->__default_prefix, $this->__default_ns); } if (($elements = $this->__value->xpath($xpath)) !== false) { if ($limit == 1 && count($elements) > 0) return new clNode($elements[0]); $nodes = array(); foreach($elements as $i => $el) { if ($limit != null && $i == $limit) break; $nodes[] = new clNode($el, null, $this->__default_prefix, $this->__default_ns); } return $nodes; } else { return new clNode(); } } /** * @since 1.1.0 */ function first($xpath) { return $this->xpath($xpath, 1); } function get($path = null, $limit = null, $forgive = false) { if ($path) clAPI::trace("Searching for "$path""); if ($path === null) return $this; $node = $this; foreach(split('\/', $path) as $childName) { $index = $attribute = null; if (preg_match('/^@(.*)?$/', $childName, $matches)) { // attribute only $childName = null; $attribute = $matches[1]; clAPI::trace("Searching for attribute named $attribute"); } else if (preg_match('/(.*)\[(\d+)\](@(.*))?$/', $childName, $matches)) { // array request with/out attribute $childName = $matches[1]; $index = (int) $matches[2]; $attribute = (isset($matches[4])) ? $matches[4] : null; clAPI::trace("Searching for element $childName".'['."$index]".($attribute ? ", attribute $attribute" : '')); } else if (preg_match('/([^@]+)(@(.*))?$/', $childName, $matches)) { // element request with/out attribute $childName = $matches[1]; $attribute = (isset($matches[3])) ? $matches[3] : null; clAPI::trace("Searching for element $childName".($attribute ? ", attribute $attribute" : '')); } if (!$childName && $attribute) { if (!isset($node->__attributes[$attribute])) { if (!$forgive) clAPI::warn("$node->__name does not have an attribute named $attribute"); return null; } return (isset($node->__attributes[$attribute]) ? $node->__attributes[$attribute] : null); } if ($childName && is_array($node)) { if (!$forgive) clAPI::error("You are looking for $childName in an array of elements, which isn't possible"); return new clNode(); } else if (!isset($node->__children[$childName])) { if (!$forgive) clAPI::error("$childName is not a child of $node->__name"); return new clNode(); } else { $node = $node->__children[$childName]; if ($index !== null) { if ($index === 0 && !is_array($node) && is_object($node)) { $node = $node; // weird, eh? } else if (is_array($index) && $index > count($node)-1) { if (!$forgive) clAPI::warn("$node->__name did not have an element at index $index"); return new clNode(); } else if (!is_array($node)) { if (!$forgive) clAPI::error("$node->__name is not an array of elements"); return new clNode(); } else $node = $node[$index]; } if ($attribute !== null) { if (!count($node->__attributes)) { if (!$forgive) clAPI::warn("$childName does not have any attributes"); return null; } if (!isset($node->__attributes[$attribute])) { if (!$forgive) clAPI::warn("$node->__name does not have an attribute named $attribute"); return null; } return isset($node->__attributes[$attribute]) ? $node->__attributes[$attribute] : null; } } } if (is_array($node) && is_numeric($limit)) return array_slice($node, 0, $limit); else return $node; } function text($path = null, $limit = null) { $node = $this->get($path, $limit); if (is_array($node)) return '['.join(', ', $node).']'; else return $node; } function info($path = null, $limit = null, $source = null) { if (!self::$jqueryOut) { ?>
[+] '.$source; $node = $this->get($path, $limit); if (is_array($node)) { foreach($node as $n) self::blockquote($n->__name, $n); } else if (is_object($node)) self::blockquote($node->__name, $node, ($source)); else if ($node !== null) echo "$path: $node"; if ($source) echo '
'; return ''; } private static function blockquote($name, $node, $hide = true) { if (is_object($node)) { $attributes = array(); foreach($node->__attributes as $n => $v) $attributes[] = "$n="".htmlentities($v)."""; echo '
<'.(count($node->__children) ? ''.$name.'' : ''.$name.'').(count($attributes) ? ' '.join(' ', $attributes) : '').'>'; echo htmlentities(trim($node->__value[0])); foreach($node->__children as $childName => $child) self::blockquote($childName, $child); echo '</'.$name.'>
'; } else if (is_array($node)) { foreach($node as $instance) self::blockquote($name, $instance); } } function __get($name) { $sxml = $this->__value; return $sxml->$name; } function __toString() { ob_start(); echo $this->__value; $content = ob_get_clean(); return ($content !== null ? $content : ''); } function renderTwitterLink($anchorText = '»') { $text = $this->get('text'); $text = preg_replace('#http://[^ ]+#i', '\\0', $text); $text = preg_replace('/@([a-z0-9_]+)/i', '\\0', $text); return $text.' '.$anchorText.''; } function rewind() { $this->__position = 0; } function current() { if ($this->__position == 0) return $this; } function key() { return 0; } function next() { ++$this->__position; } function valid() { return ($this->__position == 0 && $this->__value); } } /** * Cache whatever, dude. * @package coreylib * @version 1.0.0 */ class clCache { private static $currentNameQueue = array(); private static $mysqlTableExists = null; private static $mysqlConnection; private static $inMem = array(); public static $options = array( 'nocreate' => false, 'mysql_host' => '', 'mysql_database' => '', 'mysql_username' => '', 'mysql_password' => '', 'mysql_table_prefix' => 'coreylib_' ); static function configure($option_name_or_array, $value_or_null = null) { if (is_array($option_name_or_array)) self::$options = array_merge(self::$options, $option_name_or_array); else self::$options[$option_name_or_array] = $value_or_null; } /** * Prepare the local filesystem for caching our stuff. * @param $name The unique identifier from which to generate a unique cache file * @returns mixed When initialization fails, returns false; otherwise, returns an array with three parameters: the new file name, the path to the cache folder, and the path to the cache file. */ private static function initFileSystem($name) { $fileName = md5($name); $cachePath = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR.'.coreycache'; if (!file_exists($cachePath)) { if (@mkdir($cachePath) === false) { clAPI::error("Failed to create cache folder $cachePath"); return false; } } $pathToFile = $cachePath.DIRECTORY_SEPARATOR.$fileName; return array($fileName, $cachePath, $pathToFile); } /** * Retrieves the value of the first column of the first row in the query result. * @return When now rows are returned by the query, null; otherwise, returns the first value. */ private static function getVar($query) { if ($result = @mysql_query($query, self::$mysqlConnection)) { $arr = mysql_fetch_array($result); return $arr[0]; } else return null; } /** * @return The coreylib cache table name, with the proper prefix appended. */ private static function cacheTableName() { return (isset(self::$options['mysql_table_prefix']) ? self::$options['mysql_table_prefix'] : '')."cache"; } /** * Initialize MySQL caching: connect to the database and create the cache table if needed. * @return When initialization succeeds, true; otherwise, false. */ private static function initMySql() { if (self::$mysqlTableExists === null) { clAPI::trace('Initializing MySQL cache.'); if (!(self::$mysqlConnection = mysql_connect(self::$options['mysql_host'], self::$options['mysql_username'], self::$options['mysql_password']))) { clAPI::error('Failed to connect to the MySQL server.'); return false; } if (!@mysql_select_db(self::$options['mysql_database'], self::$mysqlConnection)) { clAPI::error('Unable to select database `'.self::$options['mysql_database'].'`'); return false; } if (!self::$options['nocreate']) { self::$mysqlTableExists = self::cacheTableName() == self::getVar("SHOW TABLES LIKE '".self::cacheTableName()."'"); if (!self::$mysqlTableExists) { clAPI::trace('Creating cache table '.self::cacheTableName().''); if (!@mysql_query('CREATE TABLE `'.self::cacheTableName().'` (`id` VARCHAR(32) NOT NULL PRIMARY KEY, `cached_on` DATETIME NOT NULL, `content` LONGBLOB)', self::$mysqlConnection)) { clAPI::error('Failed to create cache table `'.self::cacheTableName().'`: '.mysql_error(self::$mysqlConnection)); return false; } else self::$mysqlTableExists = true; } else return true; } else { self::$mysqlTableExists = true; clAPI::warn("You didn't let coreylib check to see if the cache table was there. Hope you're right."); return true; } } else return self::$mysqlTableExists; } /** * Parses $cacheFor into a timestamp, representing a date in the past. * @param $cache A value that strtotime() understand * @return a Unix timestamp, or false when unable to parse $cacheFor */ private static function parseCacheFor($cacheFor) { if (!is_numeric($cacheFor)) { $original = trim($cacheFor); $firstChar = substr($cacheFor, 0, 1); if ($firstChar == "+") { $cacheFor = '-'.substr($cacheFor, 1); } else if ($firstChar != "-") { if (stripos($cacheFor, 'last') === false) $cacheFor = '-'.$cacheFor; } if (($cacheFor = strtotime(gmdate('c', strtotime($cacheFor)))) === false) { clAPI::error("I don't understand $original as an expression of time."); return false; } return $cacheFor; } else { $cacheFor = strtotime(gmdate())-$cacheFor; return $cacheFor; } } private static function getFileCacheUnlessIsOldOrDoesNotExist($name, $cacheFor) { if (!$cacheFor) return false; if (!($cacheFor = self::parseCacheFor($cacheFor))) return false; $init = self::initFileSystem($name); if ($init == false) return false; else list($fileName, $cachePath, $pathToFile) = $init; if (isset(self::$inMem[$fileName])) { clAPI::debug("File cache $fileName found in memory! Now that's fast."); return self::$inMem[$fileName]; } if (!file_exists($pathToFile)) { // file does not exist clAPI::debug("File cache $fileName does not exist."); return false; } if (($fileAge = @filemtime($pathToFile)) === false) { // couldn't read the file last-modified time: have no idea how old the file is! clAPI::error("Unable to read file modification time of $pathToFile"); return false; } $content = ($cacheFor < strtotime(gmdate('c', $fileAge))) ? @file_get_contents($pathToFile) : false; if ($content === false) clAPI::debug("File cache $fileName was too old."); else self::$inMem[$fileName] = $content; return $content; } private static function getMysqlCacheUnlessIsOldOrDoesNotExist($name, $cacheFor) { if (!$cacheFor) return false; $md5 = md5($name); if (isset(self::$inMem[$md5])) { clAPI::debug("MySQL cache $md5 found in memory! Now that's fast."); return self::$inMem[$md5]; } if (self::initMySql()) { $cacheFor = date('Y/m/d H:i:s', self::parseCacheFor($cacheFor)); clAPI::debug("Querying MySQL for cached content named $md5 cached no earlier than $cacheFor."); $content = self::getVar("SELECT content FROM `".self::cacheTableName()."` WHERE id='$md5' AND '$cacheFor' < cached_on"); if ($error = mysql_error(self::$mysqlConnection)) { clAPI::error('Failed to query the database for cached content. See error log for details'); error_log('Failed to query the database for cached content: '.$error); return false; } else if ($content === null) { clAPI::debug("No MySQL cached content found for $md5"); return false; } else { clAPI::debug("MySQL cached content found for $md5. Yippie!"); self::$inMem[$md5] = $content; return $content; } } else return false; } private static function getCacheUnlessIsOldOrDoesNotExist($name, $cacheFor) { return (self::isMysqlMode() ? self::getMysqlCacheUnlessIsOldOrDoesNotExist($name, $cacheFor) : self::getFileCacheUnlessIsOldOrDoesNotExist($name, $cacheFor)); } /** * If cached data exists for $name within $cacheFor, if $return is true, return the cached data, otherwise print it to the output buffer. * @param $name String The unique name underwhich the cached data is expected to be stored * @param $cacheFor String A string-representation of a point in the past, best expressed in terms of minutes, hours, days, weeks, or months, e.g., "1 minute" or "2 days" - anything that strtotime() can understand. * @param $return boolean When true, if cached data is found, that cached data is returned by the function instead of being printed to the output buffer * @return When no data is cached under $name or when cached data is older than $cacheFor, returns false; otherwise, returns data according to the value of $return. */ static function cached($name, $cacheFor = 0, $return = false) { clAPI::trace("Looking for cached content $name no older than $cacheFor."); if (empty($name)) return false; if (($content = self::getCacheUnlessIsOldOrDoesNotExist($name, $cacheFor)) === false) { if (!$return) { self::$currentNameQueue[] = $name; ob_start(); } return false; } else { if ($return) return $content; else { echo $content; return true; } } } static function flush($name) { if (self::isMysqlMode()) { if (!self::initMySql()) return false; else { $md5 = md5($name); if (!@mysql_query("DELETE FROM `".self::cacheTableName()."` WHERE id='$md5'", self::$mysqlConnection) === false && $error = mysql_error(self::$mysqlConnection)) { clAPI::error("Failed to flush cache $md5. See server error log for details."); error_log("Failed to flush cache [$md5]: $error"); return false; } else return true; } } else { if (!($init = self::initFileSystem($name))) return false; else list($fileName, $cachePath, $pathToFile) = $init; if (file_exists($pathToFile) && !@unlink($pathToFile)) { clAPI::error("Failed to delete cache file for $name."); return false; } else return true; } } public static function saveContent($name, $content, $cacheFor = 0) { if (self::isMysqlMode()) { // mysql cache if (!self::initMySql()) return false; else { $md5 = md5($name); $now = gmdate('Y/m/d H:i:s'); if (!@mysql_query("REPLACE INTO `".self::cacheTableName()."` (id, cached_on, content) VALUES ('$md5', '$now', '".mysql_escape_string($content)."')", self::$mysqlConnection) && $error = mysql_error(self::$mysqlConnection)) { clAPI::error("Failed to cache $md5. See server error log for details."); error_log("Failed to cache [$md5]: $error"); return false; } else return true; } } else { // file system cache if (!($init = self::initFileSystem($name))) return false; else list($fileName, $cachePath, $pathToFile) = $init; if (@file_put_contents($pathToFile, $content) === false) { clAPI::error("Failed to save cache file $pathToFile."); return false; } else return true; } } public static function save() { $content = ob_get_flush(); self::saveContent(array_pop(self::$currentNameQueue), $content, 0); } public static function cancel() { ob_end_flush(); } private static function isMysqlMode() { return ( @strlen(self::$options['mysql_host']) && @strlen(self::$options['mysql_database']) && @strlen(self::$options['mysql_username']) && @strlen(self::$options['mysql_password']) ); } } if (!function_exists('cached')) { /** * If cached data exists for $name within $cacheFor: if $return is true, return the cached data, otherwise print it to the output buffer. * @param $name String The unique name underwhich the cached data is expected to be stored * @param $cacheFor String A string-representation of a point in the past, best expressed in terms of minutes, hours, days, weeks, or months, e.g., "1 minute" or "2 days" - anything that strtotime() can understand. * @param $return boolean When true, if cached data is found, that cached data is returned by the function instead of being printed to the output buffer * @return When no data is cached under $name or when cached data is older than $cacheFor, returns false; otherwise, returns data according to the value of $return. */ function cached($name, $cacheFor = 0, $return = false) { return clCache::cached($name, $cacheFor, $return); } } if (!function_exists('save')) { function save() { clCache::save(); } } class clMashup implements ArrayAccess, Iterator { private $spuds = array(); private $fries = array(); function __construct($items_at = null, $sort_by = null, $urls = array()) { foreach($urls as $i => $url) { $spud = new clSpud($i, new clAPI($url), $items_at, $sort_by); $this->spuds[] = $spud; } } function add($source, clAPI $api, $items_at = null, $sort_by = null) { $spud = new clSpud($source, $api, $items_at, $sort_by); $this->spuds[] = $spud; return $spud; } function info() { foreach($this->spuds as $s) $s->api()->info(); } function count() { return count($this->fries); } function parse($cacheFor = 0) { $success = true; $mh = curl_multi_init(); $spuds_to_parse_now = array(); $curl_handles = array(); $queued_spuds = array(); foreach($this->spuds as $i => $spud) { $from_parser = $spud->api()->queue($cacheFor); if (!is_string($from_parser)) { // store reference to spud for later parsing $url = curl_getinfo($from_parser, CURLINFO_EFFECTIVE_URL); $key = md5($i.$url); $queued_spuds[$key] = $spud; $curl_handles[$i] = $from_parser; // queue in multi handle curl_multi_add_handle($mh, $from_parser); } else { $spud->api()->parseText($from_parser, true); $spuds_to_parse_now[] = $spud; } } // go, go gadget, multi-curl! $running = count($curl_handles); curl_multi_exec($mh, $running); // doesn't block // before we start waiting for queued curl requests, parse the one's we had cached content for foreach($spuds_to_parse_now as $spud) { $this->makeFries($spud); } // wait for curl to finish if ($running > 0) { do { curl_multi_exec($mh, $running); } while ($running > 0); } foreach($curl_handles as $i => $ch) { $key = md5($i.curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); $content = curl_multi_getcontent($ch); $spud = $queued_spuds[$key]; $spud->api()->parseText($content, false); $this->makeFries($spud); } } private function makeFries(clSpud $spud) { if ($items_at = $spud->getItemsAt()) { foreach($spud->api()->get($items_at) as $node) { $this->fries[] = new clFry($spud, $node); } } } function sort($order = 'descending', $type = MASHUP_SORT_DATE) { $arr = array(); foreach($this->fries as $fry) { if ($sort_by = $fry->spud->getSortOn()) { $key = ''.$fry->get($sort_by); if ($type == MASHUP_SORT_DATE) $key = date('c', strtotime($key)); while(isset($arr[$key])) $key .= 'a'; $arr[$key] = $fry; } else { $key = 0; while (isset($arr[$key])) $key .= 'a'; $arr[$key] = $fry; } } if (preg_match('/desc.*/i', $order)) krsort($arr); else ksort($arr); $this->fries = array(); $keys = array_keys($arr); for($i=0; $ifries[] = $arr[$keys[$i]]; } private $i = 0; private $limit = null; function limit($limit) { $this->limit = $limit; return $this; } function clearLimit() { $this->limit = null; return $this; } function current() { return $this->fries[$this->i]; } function key() { return $this->i; } function next() { $this->i++; } function rewind() { $this->i = 0; } function valid() { return ($this->limit == null || ($this->i < $this->limit)) && isset($this->fries[$this->i]); } function offsetExists($offset) { return isset($this->fries[$offset]); } function offsetGet($offset) { return $this->fries[$offset]; } function offsetSet($offset, $value) { throw new Exception("Mashups are read-only."); } function offsetUnset($offset) { throw new Exception("Mashups are read-only."); } } // end clMashup class clFry { public $spud; public $api; public $node; public $source; function __construct(clSpud $spud, clNode $node) { $this->source = $spud->source(); $this->api = $spud->api(); $this->spud = $spud; $this->node = $node; } function get($path = null) { return $this->node->get($path); } function has($path = null) { return $this->node->has($path); } function info($path = null) { return $this->node->info($path); } } class clSpud { private $api; private $source; private $sort_by; private $items_at; function __construct($source, clAPI $api, $items_at = null, $sort_by = null) { $this->source = $source; $this->api = $api; $this->items_at = $items_at; $this->sort_by = $sort_by; } function source() { return $this->source; } function api() { return $this->api; } function sort_by($sort_by) { $this->sort_by = $sort_by; return $this; } function items_at($items_at) { $this->items_at = $items_at; return $this; } function getItemsAt() { return $this->items_at; } function getSortOn() { return $this->sort_by; } }