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']) { ?>
[+] '.$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; $i