--- /dev/null
+/config.php
--- /dev/null
+<?php
+
+function comp_blog() {
+ echo '<h2>latest blog posts</h2>';
+ if ($data = parse_rss(get_config('blog_rss'))) {
+ echo '<ul>';
+ foreach ($data->content as $item) {
+ echo '<li><a href="' . $item->link . '">' . $item->title . '</a></li>';
+ }
+ echo '<li class="last"><a href="http://she.geek.nz/"> ... more</a></li>';
+ echo '</ul>';
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+
+function comp_flickr() {
+ require_once(get_config('lib') . 'phpFlickr.php');
+ $f = new phpFlickr(get_config('flickr_api_key'));
+ $f->enableCache("fs", get_config('flickr_cache'), get_config('cache_lifetime'));
+
+ // Find the NSID of the username
+
+ $person = $f->people_findByUsername(get_config('flickr_username'));
+ $photos_url = $f->urls_getUserPhotos($person['id']);
+ $photos = $f->people_getPublicPhotos($person['id'], NULL, 9);
+
+ foreach ((array)$photos['photo'] as $photo) {
+ echo "<a href=$photos_url$photo[id]>";
+ echo "<img border='0' alt='$photo[title]' ".
+ "src=" . $f->buildPhotoURL($photo, "Square") . ">";
+ echo "</a>";
+ $i++;
+ }
+}
--- /dev/null
+<?php
+
+function comp_lastfm() {
+ echo '<h2>recently listened tracks</h2>';
+ if ($data = parse_rss('http://ws.audioscrobbler.com/1.0/user/' . get_config('lastfm_username') . '/recenttracks.rss')) {
+ echo '<ul>';
+ foreach ($data->content as $item) {
+ echo '<li><a href="' . $item->link . '">' . $item->title . '</a></li>';
+ }
+ echo '<li class="last"><a href="http://last.fm/user/mjollnir_/">... more</a></li>';
+ echo '</ul>';
+ }
+
+}
--- /dev/null
+<?php
+
+function comp_main() {
+ include(get_config('root') . '/include/main.inc');
+}
--- /dev/null
+<?php
+
+function comp_twitter() {
+ echo '<h2>latest twitter updates</h2>';
+ if ($data = parse_rss('http://twitter.com/statuses/user_timeline/' . get_config('twitter_username') . '.rss')) {
+ echo '<ul>';
+ foreach ($data->content as $item) {
+ echo '<li><a href="' . $item->link . '">' . substr($item->title, strlen(get_config('twitter_username')) + 2) . '</a></li>';
+ }
+ echo '<li class="last"><a href="http://twitter.com/mjollnir/">... more</a></li>';
+ echo '</ul>';
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+$CFG = array(
+ 'flickr_api_key' => '',
+ 'flickr_api_secret' => '',
+ 'flickr_username' => '',
+ 'flickr_cache' => '',
+ 'twitter_username' => '',
+ 'lastfm_username' => '',
+ 'blog_rss' => '',
+ 'rss_cache' => '',
+ 'curl' => '',
+ 'cache_lifetime' => 0,
+);
+
+// autodetect stuff
+$CFG['lib'] = dirname(__FILE__) . '/lib/';
+$CFG['root'] = dirname(__FILE__) . '/';
+$CFG['comp'] = dirname(__FILE__) . '/comp/';
+
+?>
--- /dev/null
+<?php
+ $whorepile = array(
+ 'twitter' => 'http://twitter.com/mjollnir',
+ 'facebook' => 'http://www.facebook.com/profile.php?id=571239084',
+ 'last.fm' => 'http://www.last.fm/user/mjollnir_/',
+ 'flickr' => 'http://www.flickr.com/photos/penzilinha/',
+ 'friendfeed' => 'http://friendfeed.com/mjollnir',
+ 'pownce' => 'http://pownce.com/mjollnir/',
+ 'ohloh' => 'http://www.ohloh.net/accounts/3438',
+);
+
+?>
+<h2>mjollnir.org</h2>
+<p>a collection of what I'm doing all over the internet, mashed together into one place for your viewing pleasure.</p>
+<p>my blog lives at <a href="http://she.geek.nz">she.geek.nz</a>. there's an <a href="http://she.geek.nz/about/">about page</a> there too. it's old and rather out of date.</p>
+<p>find me on <?php
+ $count = 0;
+ foreach ($whorepile as $service => $url) {
+ echo '<a href="' . $url . '">' . $service . '</a>';
+ if ($count != count($whorepile) -1) {
+ echo ', ';
+ }
+ $count++;
+ }
+?>. christ. at least I never got livejournal.</p>
+<p>I don't have an rss feed for this mashup. use friendfeed instead, or an individual site's feed.</p>
+<p>thanks to <a href="http://nothing.net.nz">vex</a> for the vserver</p>
+<p><a href="mailto:penny@mjollnir.org">send me email!</a>
+<span class="mind"><b>On my mind</b>: <a href="http://mahara.org">mahara</a>, more tattoos, less tattoos, moodle gsoc, Europe</span>
--- /dev/null
+<?php
+ require_once(dirname(__FILE__) . '/config.php');
+ require_once(dirname(__FILE__) . '/lib/general.php');
+ $components = get_components();
+echo '<?xml version="1.0" encoding="UTF-8"?>';
+?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>mjollnir.org</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ </head>
+ <body>
+<?php
+ foreach ($components as $comp) {
+ echo ' <div class="component" id="' . $comp . '">';
+ require_once(get_config('comp') . $comp . '.php');
+ call_user_func('comp_' . $comp);
+ echo ' </div>';
+ }
+?>
+ </body>
+</html>
--- /dev/null
+<?php
+
+/**
+* Fast, light and safe Cache Class
+*
+* Cache_Lite is a fast, light and safe cache system. It's optimized
+* for file containers. It is fast and safe (because it uses file
+* locking and/or anti-corruption tests).
+*
+* There are some examples in the 'docs/examples' file
+* Technical choices are described in the 'docs/technical' file
+*
+* Memory Caching is from an original idea of
+* Mike BENOIT <ipso@snappymail.ca>
+*
+* Nota : A chinese documentation (thanks to RainX <china_1982@163.com>) is
+* available at :
+* http://rainx.phpmore.com/manual/cache_lite.html
+*
+* @package Cache_Lite
+* @category Caching
+* @version $Id: Lite.php,v 1.50 2008/04/13 14:41:23 tacker Exp $
+* @author Fabien MARTY <fab@php.net>
+*/
+
+define('CACHE_LITE_ERROR_RETURN', 1);
+define('CACHE_LITE_ERROR_DIE', 8);
+
+class Cache_Lite
+{
+
+ // --- Private properties ---
+
+ /**
+ * Directory where to put the cache files
+ * (make sure to add a trailing slash)
+ *
+ * @var string $_cacheDir
+ */
+ var $_cacheDir = '/tmp/';
+
+ /**
+ * Enable / disable caching
+ *
+ * (can be very usefull for the debug of cached scripts)
+ *
+ * @var boolean $_caching
+ */
+ var $_caching = true;
+
+ /**
+ * Cache lifetime (in seconds)
+ *
+ * If null, the cache is valid forever.
+ *
+ * @var int $_lifeTime
+ */
+ var $_lifeTime = 3600;
+
+ /**
+ * Enable / disable fileLocking
+ *
+ * (can avoid cache corruption under bad circumstances)
+ *
+ * @var boolean $_fileLocking
+ */
+ var $_fileLocking = true;
+
+ /**
+ * Timestamp of the last valid cache
+ *
+ * @var int $_refreshTime
+ */
+ var $_refreshTime;
+
+ /**
+ * File name (with path)
+ *
+ * @var string $_file
+ */
+ var $_file;
+
+ /**
+ * File name (without path)
+ *
+ * @var string $_fileName
+ */
+ var $_fileName;
+
+ /**
+ * Enable / disable write control (the cache is read just after writing to detect corrupt entries)
+ *
+ * Enable write control will lightly slow the cache writing but not the cache reading
+ * Write control can detect some corrupt cache files but maybe it's not a perfect control
+ *
+ * @var boolean $_writeControl
+ */
+ var $_writeControl = true;
+
+ /**
+ * Enable / disable read control
+ *
+ * If enabled, a control key is embeded in cache file and this key is compared with the one
+ * calculated after the reading.
+ *
+ * @var boolean $_writeControl
+ */
+ var $_readControl = true;
+
+ /**
+ * Type of read control (only if read control is enabled)
+ *
+ * Available values are :
+ * 'md5' for a md5 hash control (best but slowest)
+ * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
+ * 'strlen' for a length only test (fastest)
+ *
+ * @var boolean $_readControlType
+ */
+ var $_readControlType = 'crc32';
+
+ /**
+ * Pear error mode (when raiseError is called)
+ *
+ * (see PEAR doc)
+ *
+ * @see setToDebug()
+ * @var int $_pearErrorMode
+ */
+ var $_pearErrorMode = CACHE_LITE_ERROR_RETURN;
+
+ /**
+ * Current cache id
+ *
+ * @var string $_id
+ */
+ var $_id;
+
+ /**
+ * Current cache group
+ *
+ * @var string $_group
+ */
+ var $_group;
+
+ /**
+ * Enable / Disable "Memory Caching"
+ *
+ * NB : There is no lifetime for memory caching !
+ *
+ * @var boolean $_memoryCaching
+ */
+ var $_memoryCaching = false;
+
+ /**
+ * Enable / Disable "Only Memory Caching"
+ * (be carefull, memory caching is "beta quality")
+ *
+ * @var boolean $_onlyMemoryCaching
+ */
+ var $_onlyMemoryCaching = false;
+
+ /**
+ * Memory caching array
+ *
+ * @var array $_memoryCachingArray
+ */
+ var $_memoryCachingArray = array();
+
+ /**
+ * Memory caching counter
+ *
+ * @var int $memoryCachingCounter
+ */
+ var $_memoryCachingCounter = 0;
+
+ /**
+ * Memory caching limit
+ *
+ * @var int $memoryCachingLimit
+ */
+ var $_memoryCachingLimit = 1000;
+
+ /**
+ * File Name protection
+ *
+ * if set to true, you can use any cache id or group name
+ * if set to false, it can be faster but cache ids and group names
+ * will be used directly in cache file names so be carefull with
+ * special characters...
+ *
+ * @var boolean $fileNameProtection
+ */
+ var $_fileNameProtection = true;
+
+ /**
+ * Enable / disable automatic serialization
+ *
+ * it can be used to save directly datas which aren't strings
+ * (but it's slower)
+ *
+ * @var boolean $_serialize
+ */
+ var $_automaticSerialization = false;
+
+ /**
+ * Disable / Tune the automatic cleaning process
+ *
+ * The automatic cleaning process destroy too old (for the given life time)
+ * cache files when a new cache file is written.
+ * 0 => no automatic cache cleaning
+ * 1 => systematic cache cleaning
+ * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write
+ *
+ * @var int $_automaticCleaning
+ */
+ var $_automaticCleaningFactor = 0;
+
+ /**
+ * Nested directory level
+ *
+ * Set the hashed directory structure level. 0 means "no hashed directory
+ * structure", 1 means "one level of directory", 2 means "two levels"...
+ * This option can speed up Cache_Lite only when you have many thousands of
+ * cache file. Only specific benchs can help you to choose the perfect value
+ * for you. Maybe, 1 or 2 is a good start.
+ *
+ * @var int $_hashedDirectoryLevel
+ */
+ var $_hashedDirectoryLevel = 0;
+
+ /**
+ * Umask for hashed directory structure
+ *
+ * @var int $_hashedDirectoryUmask
+ */
+ var $_hashedDirectoryUmask = 0700;
+
+ /**
+ * API break for error handling in CACHE_LITE_ERROR_RETURN mode
+ *
+ * In CACHE_LITE_ERROR_RETURN mode, error handling was not good because
+ * for example save() method always returned a boolean (a PEAR_Error object
+ * would be better in CACHE_LITE_ERROR_RETURN mode). To correct this without
+ * breaking the API, this option (false by default) can change this handling.
+ *
+ * @var boolean
+ */
+ var $_errorHandlingAPIBreak = false;
+
+ // --- Public methods ---
+
+ /**
+ * Constructor
+ *
+ * $options is an assoc. Available options are :
+ * $options = array(
+ * 'cacheDir' => directory where to put the cache files (string),
+ * 'caching' => enable / disable caching (boolean),
+ * 'lifeTime' => cache lifetime in seconds (int),
+ * 'fileLocking' => enable / disable fileLocking (boolean),
+ * 'writeControl' => enable / disable write control (boolean),
+ * 'readControl' => enable / disable read control (boolean),
+ * 'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string),
+ * 'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int),
+ * 'memoryCaching' => enable / disable memory caching (boolean),
+ * 'onlyMemoryCaching' => enable / disable only memory caching (boolean),
+ * 'memoryCachingLimit' => max nbr of records to store into memory caching (int),
+ * 'fileNameProtection' => enable / disable automatic file name protection (boolean),
+ * 'automaticSerialization' => enable / disable automatic serialization (boolean),
+ * 'automaticCleaningFactor' => distable / tune automatic cleaning process (int),
+ * 'hashedDirectoryLevel' => level of the hashed directory system (int),
+ * 'hashedDirectoryUmask' => umask for hashed directory structure (int),
+ * 'errorHandlingAPIBreak' => API break for better error handling ? (boolean)
+ * );
+ *
+ * @param array $options options
+ * @access public
+ */
+ function Cache_Lite($options = array(NULL))
+ {
+ foreach($options as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+
+ /**
+ * Generic way to set a Cache_Lite option
+ *
+ * see Cache_Lite constructor for available options
+ *
+ * @var string $name name of the option
+ * @var mixed $value value of the option
+ * @access public
+ */
+ function setOption($name, $value)
+ {
+ $availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode');
+ if (in_array($name, $availableOptions)) {
+ $property = '_'.$name;
+ $this->$property = $value;
+ }
+ }
+
+ /**
+ * Test if a cache is available and (if yes) return it
+ *
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string data of the cache (else : false)
+ * @access public
+ */
+ function get($id, $group = 'default', $doNotTestCacheValidity = false)
+ {
+ $this->_id = $id;
+ $this->_group = $group;
+ $data = false;
+ if ($this->_caching) {
+ $this->_setRefreshTime();
+ $this->_setFileName($id, $group);
+ clearstatcache();
+ if ($this->_memoryCaching) {
+ if (isset($this->_memoryCachingArray[$this->_file])) {
+ if ($this->_automaticSerialization) {
+ return unserialize($this->_memoryCachingArray[$this->_file]);
+ }
+ return $this->_memoryCachingArray[$this->_file];
+ }
+ if ($this->_onlyMemoryCaching) {
+ return false;
+ }
+ }
+ if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) {
+ if (file_exists($this->_file)) {
+ $data = $this->_read();
+ }
+ } else {
+ if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) {
+ $data = $this->_read();
+ }
+ }
+ if (($data) and ($this->_memoryCaching)) {
+ $this->_memoryCacheAdd($data);
+ }
+ if (($this->_automaticSerialization) and (is_string($data))) {
+ $data = unserialize($data);
+ }
+ return $data;
+ }
+ return false;
+ }
+
+ /**
+ * Save some data in a cache file
+ *
+ * @param string $data data to put in cache (can be another type than strings if automaticSerialization is on)
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @return boolean true if no problem (else : false or a PEAR_Error object)
+ * @access public
+ */
+ function save($data, $id = NULL, $group = 'default')
+ {
+ if ($this->_caching) {
+ if ($this->_automaticSerialization) {
+ $data = serialize($data);
+ }
+ if (isset($id)) {
+ $this->_setFileName($id, $group);
+ }
+ if ($this->_memoryCaching) {
+ $this->_memoryCacheAdd($data);
+ if ($this->_onlyMemoryCaching) {
+ return true;
+ }
+ }
+ if ($this->_automaticCleaningFactor>0) {
+ $rand = rand(1, $this->_automaticCleaningFactor);
+ if ($rand==1) {
+ $this->clean(false, 'old');
+ }
+ }
+ if ($this->_writeControl) {
+ $res = $this->_writeAndControl($data);
+ if (is_bool($res)) {
+ if ($res) {
+ return true;
+ }
+ // if $res if false, we need to invalidate the cache
+ @touch($this->_file, time() - 2*abs($this->_lifeTime));
+ return false;
+ }
+ } else {
+ $res = $this->_write($data);
+ }
+ if (is_object($res)) {
+ // $res is a PEAR_Error object
+ if (!($this->_errorHandlingAPIBreak)) {
+ return false; // we return false (old API)
+ }
+ }
+ return $res;
+ }
+ return false;
+ }
+
+ /**
+ * Remove a cache file
+ *
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @return boolean true if no problem
+ * @access public
+ */
+ function remove($id, $group = 'default')
+ {
+ $this->_setFileName($id, $group);
+ if ($this->_memoryCaching) {
+ if (isset($this->_memoryCachingArray[$this->_file])) {
+ unset($this->_memoryCachingArray[$this->_file]);
+ $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
+ }
+ if ($this->_onlyMemoryCaching) {
+ return true;
+ }
+ }
+ return $this->_unlink($this->_file);
+ }
+
+ /**
+ * Clean the cache
+ *
+ * if no group is specified all cache files will be destroyed
+ * else only cache files of the specified group will be destroyed
+ *
+ * @param string $group name of the cache group
+ * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
+ * 'callback_myFunction'
+ * @return boolean true if no problem
+ * @access public
+ */
+ function clean($group = false, $mode = 'ingroup')
+ {
+ return $this->_cleanDir($this->_cacheDir, $group, $mode);
+ }
+
+ /**
+ * Set to debug mode
+ *
+ * When an error is found, the script will stop and the message will be displayed
+ * (in debug mode only).
+ *
+ * @access public
+ */
+ function setToDebug()
+ {
+ $this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE);
+ }
+
+ /**
+ * Set a new life time
+ *
+ * @param int $newLifeTime new life time (in seconds)
+ * @access public
+ */
+ function setLifeTime($newLifeTime)
+ {
+ $this->_lifeTime = $newLifeTime;
+ $this->_setRefreshTime();
+ }
+
+ /**
+ * Save the state of the caching memory array into a cache file cache
+ *
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @access public
+ */
+ function saveMemoryCachingState($id, $group = 'default')
+ {
+ if ($this->_caching) {
+ $array = array(
+ 'counter' => $this->_memoryCachingCounter,
+ 'array' => $this->_memoryCachingArray
+ );
+ $data = serialize($array);
+ $this->save($data, $id, $group);
+ }
+ }
+
+ /**
+ * Load the state of the caching memory array from a given cache file cache
+ *
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @access public
+ */
+ function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false)
+ {
+ if ($this->_caching) {
+ if ($data = $this->get($id, $group, $doNotTestCacheValidity)) {
+ $array = unserialize($data);
+ $this->_memoryCachingCounter = $array['counter'];
+ $this->_memoryCachingArray = $array['array'];
+ }
+ }
+ }
+
+ /**
+ * Return the cache last modification time
+ *
+ * BE CAREFUL : THIS METHOD IS FOR HACKING ONLY !
+ *
+ * @return int last modification time
+ */
+ function lastModified()
+ {
+ return @filemtime($this->_file);
+ }
+
+ /**
+ * Trigger a PEAR error
+ *
+ * To improve performances, the PEAR.php file is included dynamically.
+ * The file is so included only when an error is triggered. So, in most
+ * cases, the file isn't included and perfs are much better.
+ *
+ * @param string $msg error message
+ * @param int $code error code
+ * @access public
+ */
+ function raiseError($msg, $code)
+ {
+ include_once('PEAR.php');
+ return PEAR::raiseError($msg, $code, $this->_pearErrorMode);
+ }
+
+ /**
+ * Extend the life of a valid cache file
+ *
+ * see http://pear.php.net/bugs/bug.php?id=6681
+ *
+ * @access public
+ */
+ function extendLife()
+ {
+ @touch($this->_file);
+ }
+
+ // --- Private methods ---
+
+ /**
+ * Compute & set the refresh time
+ *
+ * @access private
+ */
+ function _setRefreshTime()
+ {
+ if (is_null($this->_lifeTime)) {
+ $this->_refreshTime = null;
+ } else {
+ $this->_refreshTime = time() - $this->_lifeTime;
+ }
+ }
+
+ /**
+ * Remove a file
+ *
+ * @param string $file complete file path and name
+ * @return boolean true if no problem
+ * @access private
+ */
+ function _unlink($file)
+ {
+ if (!@unlink($file)) {
+ return $this->raiseError('Cache_Lite : Unable to remove cache !', -3);
+ }
+ return true;
+ }
+
+ /**
+ * Recursive function for cleaning cache file in the given directory
+ *
+ * @param string $dir directory complete path (with a trailing slash)
+ * @param string $group name of the cache group
+ * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
+ 'callback_myFunction'
+ * @return boolean true if no problem
+ * @access private
+ */
+ function _cleanDir($dir, $group = false, $mode = 'ingroup')
+ {
+ if ($this->_fileNameProtection) {
+ $motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_';
+ } else {
+ $motif = ($group) ? 'cache_'.$group.'_' : 'cache_';
+ }
+ if ($this->_memoryCaching) {
+ foreach($this->_memoryCachingArray as $key => $v) {
+ if (strpos($key, $motif) !== false) {\r
+ unset($this->_memoryCachingArray[$key]);
+ $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
+ }
+ }
+ if ($this->_onlyMemoryCaching) {
+ return true;
+ }
+ }
+ if (!($dh = opendir($dir))) {
+ return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4);
+ }
+ $result = true;
+ while ($file = readdir($dh)) {
+ if (($file != '.') && ($file != '..')) {
+ if (substr($file, 0, 6)=='cache_') {
+ $file2 = $dir . $file;
+ if (is_file($file2)) {
+ switch (substr($mode, 0, 9)) {
+ case 'old':
+ // files older than lifeTime get deleted from cache
+ if (!is_null($this->_lifeTime)) {
+ if ((mktime() - @filemtime($file2)) > $this->_lifeTime) {
+ $result = ($result and ($this->_unlink($file2)));
+ }
+ }
+ break;
+ case 'notingrou':
+ if (strpos($file2, $motif) === false) {\r
+ $result = ($result and ($this->_unlink($file2)));
+ }
+ break;
+ case 'callback_':
+ $func = substr($mode, 9, strlen($mode) - 9);
+ if ($func($file2, $group)) {
+ $result = ($result and ($this->_unlink($file2)));
+ }
+ break;
+ case 'ingroup':
+ default:
+ if (strpos($file2, $motif) !== false) {\r
+ $result = ($result and ($this->_unlink($file2)));
+ }
+ break;
+ }
+ }
+ if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) {
+ $result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode)));
+ }
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Add some date in the memory caching array
+ *
+ * @param string $data data to cache
+ * @access private
+ */
+ function _memoryCacheAdd($data)
+ {
+ $this->_memoryCachingArray[$this->_file] = $data;
+ if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) {
+ list($key, ) = each($this->_memoryCachingArray);
+ unset($this->_memoryCachingArray[$key]);
+ } else {
+ $this->_memoryCachingCounter = $this->_memoryCachingCounter + 1;
+ }
+ }
+
+ /**
+ * Make a file name (with path)
+ *
+ * @param string $id cache id
+ * @param string $group name of the group
+ * @access private
+ */
+ function _setFileName($id, $group)
+ {
+
+ if ($this->_fileNameProtection) {
+ $suffix = 'cache_'.md5($group).'_'.md5($id);
+ } else {
+ $suffix = 'cache_'.$group.'_'.$id;
+ }
+ $root = $this->_cacheDir;
+ if ($this->_hashedDirectoryLevel>0) {
+ $hash = md5($suffix);
+ for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
+ $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
+ }
+ }
+ $this->_fileName = $suffix;
+ $this->_file = $root.$suffix;
+ }
+
+ /**
+ * Read the cache file and return the content
+ *
+ * @return string content of the cache file (else : false or a PEAR_Error object)
+ * @access private
+ */
+ function _read()
+ {
+ $fp = @fopen($this->_file, "rb");
+ if ($this->_fileLocking) @flock($fp, LOCK_SH);
+ if ($fp) {
+ clearstatcache();
+ $length = @filesize($this->_file);
+ $mqr = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ if ($this->_readControl) {
+ $hashControl = @fread($fp, 32);
+ $length = $length - 32;
+ }
+ if ($length) {
+ $data = @fread($fp, $length);
+ } else {
+ $data = '';
+ }
+ set_magic_quotes_runtime($mqr);
+ if ($this->_fileLocking) @flock($fp, LOCK_UN);
+ @fclose($fp);
+ if ($this->_readControl) {
+ $hashData = $this->_hash($data, $this->_readControlType);
+ if ($hashData != $hashControl) {
+ if (!(is_null($this->_lifeTime))) {
+ @touch($this->_file, time() - 2*abs($this->_lifeTime));
+ } else {
+ @unlink($this->_file);
+ }
+ return false;
+ }
+ }
+ return $data;
+ }
+ return $this->raiseError('Cache_Lite : Unable to read cache !', -2);
+ }
+
+ /**
+ * Write the given data in the cache file
+ *
+ * @param string $data data to put in cache
+ * @return boolean true if ok (a PEAR_Error object else)
+ * @access private
+ */
+ function _write($data)
+ {
+ if ($this->_hashedDirectoryLevel > 0) {
+ $hash = md5($this->_fileName);
+ $root = $this->_cacheDir;
+ for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
+ $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
+ if (!(@is_dir($root))) {
+ @mkdir($root, $this->_hashedDirectoryUmask);
+ }
+ }
+ }
+ $fp = @fopen($this->_file, "wb");
+ if ($fp) {
+ if ($this->_fileLocking) @flock($fp, LOCK_EX);
+ if ($this->_readControl) {
+ @fwrite($fp, $this->_hash($data, $this->_readControlType), 32);
+ }
+ $mqr = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ @fwrite($fp, $data);
+ set_magic_quotes_runtime($mqr);
+ if ($this->_fileLocking) @flock($fp, LOCK_UN);
+ @fclose($fp);
+ return true;
+ }
+ return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1);
+ }
+
+ /**
+ * Write the given data in the cache file and control it just after to avoir corrupted cache entries
+ *
+ * @param string $data data to put in cache
+ * @return boolean true if the test is ok (else : false or a PEAR_Error object)
+ * @access private
+ */
+ function _writeAndControl($data)
+ {
+ $result = $this->_write($data);
+ if (is_object($result)) {
+ return $result; #Â We return the PEAR_Error object
+ }
+ $dataRead = $this->_read();
+ if (is_object($dataRead)) {
+ return $dataRead; #Â We return the PEAR_Error object
+ }
+ if ((is_bool($dataRead)) && (!$dataRead)) {
+ return false;
+ }
+ return ($dataRead==$data);
+ }
+
+ /**
+ * Make a control key with the string containing datas
+ *
+ * @param string $data data
+ * @param string $controlType type of control 'md5', 'crc32' or 'strlen'
+ * @return string control key
+ * @access private
+ */
+ function _hash($data, $controlType)
+ {
+ switch ($controlType) {
+ case 'md5':
+ return md5($data);
+ case 'crc32':
+ return sprintf('% 32d', crc32($data));
+ case 'strlen':
+ return sprintf('% 32d', strlen($data));
+ default:
+ return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5);
+ }
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+
+/**
+* This class extends Cache_Lite and offers a cache system driven by a master file
+*
+* With this class, cache validity is only dependent of a given file. Cache files
+* are valid only if they are older than the master file. It's a perfect way for
+* caching templates results (if the template file is newer than the cache, cache
+* must be rebuild...) or for config classes...
+* There are some examples in the 'docs/examples' file
+* Technical choices are described in the 'docs/technical' file
+*
+* @package Cache_Lite
+* @version $Id: File.php,v 1.3 2005/12/04 16:03:55 fab Exp $
+* @author Fabien MARTY <fab@php.net>
+*/
+
+require_once('Cache/Lite.php');
+
+class Cache_Lite_File extends Cache_Lite
+{
+
+ // --- Private properties ---
+
+ /**
+ * Complete path of the file used for controlling the cache lifetime
+ *
+ * @var string $_masterFile
+ */
+ var $_masterFile = '';
+
+ /**
+ * Masterfile mtime
+ *
+ * @var int $_masterFile_mtime
+ */
+ var $_masterFile_mtime = 0;
+
+ // --- Public methods ----
+
+ /**
+ * Constructor
+ *
+ * $options is an assoc. To have a look at availables options,
+ * see the constructor of the Cache_Lite class in 'Cache_Lite.php'
+ *
+ * Comparing to Cache_Lite constructor, there is another option :
+ * $options = array(
+ * (...) see Cache_Lite constructor
+ * 'masterFile' => complete path of the file used for controlling the cache lifetime(string)
+ * );
+ *
+ * @param array $options options
+ * @access public
+ */
+ function Cache_Lite_File($options = array(NULL))
+ {
+ $options['lifetime'] = 0;
+ $this->Cache_Lite($options);
+ if (isset($options['masterFile'])) {
+ $this->_masterFile = $options['masterFile'];
+ } else {
+ return $this->raiseError('Cache_Lite_File : masterFile option must be set !');
+ }
+ if (!($this->_masterFile_mtime = @filemtime($this->_masterFile))) {
+ return $this->raiseError('Cache_Lite_File : Unable to read masterFile : '.$this->_masterFile, -3);
+ }
+ }
+
+ /**
+ * Test if a cache is available and (if yes) return it
+ *
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @return string data of the cache (or false if no cache available)
+ * @access public
+ */
+ function get($id, $group = 'default')
+ {
+ if ($data = parent::get($id, $group, true)) {
+ if ($filemtime = $this->lastModified()) {
+ if ($filemtime > $this->_masterFile_mtime) {
+ return $data;
+ }
+ }
+ }
+ return false;
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+
+/**
+* This class extends Cache_Lite and can be used to cache the result and output of functions/methods
+*
+* This class is completly inspired from Sebastian Bergmann's
+* PEAR/Cache_Function class. This is only an adaptation to
+* Cache_Lite
+*
+* There are some examples in the 'docs/examples' file
+* Technical choices are described in the 'docs/technical' file
+*
+* @package Cache_Lite
+* @version $Id: Function.php,v 1.11 2006/12/14 12:59:43 cweiske Exp $
+* @author Sebastian BERGMANN <sb@sebastian-bergmann.de>
+* @author Fabien MARTY <fab@php.net>
+*/
+
+require_once('Cache/Lite.php');
+
+class Cache_Lite_Function extends Cache_Lite
+{
+
+ // --- Private properties ---
+
+ /**
+ * Default cache group for function caching
+ *
+ * @var string $_defaultGroup
+ */
+ var $_defaultGroup = 'Cache_Lite_Function';
+
+ /**
+ * Don't cache the method call when its output contains the string "NOCACHE"
+ *
+ * if set to true, the output of the method will never be displayed (because the output is used
+ * to control the cache)
+ *
+ * @var boolean $_dontCacheWhenTheOutputContainsNOCACHE
+ */
+ var $_dontCacheWhenTheOutputContainsNOCACHE = false;
+
+ /**
+ * Don't cache the method call when its result is false
+ *
+ * @var boolean $_dontCacheWhenTheResultIsFalse
+ */
+ var $_dontCacheWhenTheResultIsFalse = false;
+
+ /**
+ * Don't cache the method call when its result is null
+ *
+ * @var boolean $_dontCacheWhenTheResultIsNull
+ */
+ var $_dontCacheWhenTheResultIsNull = false;
+
+ /**
+ * Debug the Cache_Lite_Function caching process
+ *
+ * @var boolean $_debugCacheLiteFunction
+ */
+ var $_debugCacheLiteFunction = false;
+
+ // --- Public methods ----
+
+ /**
+ * Constructor
+ *
+ * $options is an assoc. To have a look at availables options,
+ * see the constructor of the Cache_Lite class in 'Cache_Lite.php'
+ *
+ * Comparing to Cache_Lite constructor, there is another option :
+ * $options = array(
+ * (...) see Cache_Lite constructor
+ * 'debugCacheLiteFunction' => (bool) debug the caching process,
+ * 'defaultGroup' => default cache group for function caching (string),
+ * 'dontCacheWhenTheOutputContainsNOCACHE' => (bool) don't cache when the function output contains "NOCACHE",
+ * 'dontCacheWhenTheResultIsFalse' => (bool) don't cache when the function result is false,
+ * 'dontCacheWhenTheResultIsNull' => (bool don't cache when the function result is null
+ * );
+ *
+ * @param array $options options
+ * @access public
+ */
+ function Cache_Lite_Function($options = array(NULL))
+ {
+ $availableOptions = array('debugCacheLiteFunction', 'defaultGroup', 'dontCacheWhenTheOutputContainsNOCACHE', 'dontCacheWhenTheResultIsFalse', 'dontCacheWhenTheResultIsNull');
+ while (list($name, $value) = each($options)) {
+ if (in_array($name, $availableOptions)) {
+ $property = '_'.$name;
+ $this->$property = $value;
+ }
+ }
+ reset($options);
+ $this->Cache_Lite($options);
+ }
+
+ /**
+ * Calls a cacheable function or method (or not if there is already a cache for it)
+ *
+ * Arguments of this method are read with func_get_args. So it doesn't appear
+ * in the function definition. Synopsis :
+ * call('functionName', $arg1, $arg2, ...)
+ * (arg1, arg2... are arguments of 'functionName')
+ *
+ * @return mixed result of the function/method
+ * @access public
+ */
+ function call()
+ {
+ $arguments = func_get_args();
+ $id = $this->_makeId($arguments);
+ $data = $this->get($id, $this->_defaultGroup);
+ if ($data !== false) {
+ if ($this->_debugCacheLiteFunction) {
+ echo "Cache hit !\n";
+ }
+ $array = unserialize($data);
+ $output = $array['output'];
+ $result = $array['result'];
+ } else {
+ if ($this->_debugCacheLiteFunction) {
+ echo "Cache missed !\n";
+ }
+ ob_start();
+ ob_implicit_flush(false);
+ $target = array_shift($arguments);
+ if (is_array($target)) {
+ // in this case, $target is for example array($obj, 'method')
+ $object = $target[0];
+ $method = $target[1];
+ $result = call_user_func_array(array(&$object, $method), $arguments);
+ } else {
+ if (strstr($target, '::')) { // classname::staticMethod
+ list($class, $method) = explode('::', $target);
+ $result = call_user_func_array(array($class, $method), $arguments);
+ } else if (strstr($target, '->')) { // object->method
+ // use a stupid name ($objet_123456789 because) of problems where the object
+ // name is the same as this var name
+ list($object_123456789, $method) = explode('->', $target);
+ global $$object_123456789;
+ $result = call_user_func_array(array($$object_123456789, $method), $arguments);
+ } else { // function
+ $result = call_user_func_array($target, $arguments);
+ }
+ }
+ $output = ob_get_contents();
+ ob_end_clean();
+ if ($this->_dontCacheWhenTheResultIsFalse) {
+ if ((is_bool($result)) && (!($result))) {
+ echo($output);
+ return $result;
+ }
+ }
+ if ($this->_dontCacheWhenTheResultIsNull) {
+ if (is_null($result)) {
+ echo($output);
+ return $result;
+ }
+ }
+ if ($this->_dontCacheWhenTheOutputContainsNOCACHE) {
+ if (strpos($output, 'NOCACHE') > -1) {
+ return $result;
+ }
+ }
+ $array['output'] = $output;
+ $array['result'] = $result;
+ $this->save(serialize($array), $id, $this->_defaultGroup);
+ }
+ echo($output);
+ return $result;
+ }
+
+ /**
+ * Drop a cache file
+ *
+ * Arguments of this method are read with func_get_args. So it doesn't appear
+ * in the function definition. Synopsis :
+ * remove('functionName', $arg1, $arg2, ...)
+ * (arg1, arg2... are arguments of 'functionName')
+ *
+ * @return boolean true if no problem
+ * @access public
+ */
+ function drop()
+ {
+ $id = $this->_makeId(func_get_args());
+ return $this->remove($id, $this->_defaultGroup);
+ }
+
+ /**
+ * Make an id for the cache
+ *
+ * @var array result of func_get_args for the call() or the remove() method
+ * @return string id
+ * @access private
+ */
+ function _makeId($arguments)
+ {
+ $id = serialize($arguments); // Generate a cache id
+ if (!$this->_fileNameProtection) {
+ $id = md5($id);
+ // if fileNameProtection is set to false, then the id has to be hashed
+ // because it's a very bad file name in most cases
+ }
+ return $id;
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+
+/**
+* This class extends Cache_Lite and uses output buffering to get the data to cache.
+*
+* There are some examples in the 'docs/examples' file
+* Technical choices are described in the 'docs/technical' file
+*
+* @package Cache_Lite
+* @version $Id: Output.php,v 1.4 2006/01/29 00:22:07 fab Exp $
+* @author Fabien MARTY <fab@php.net>
+*/
+
+require_once('Cache/Lite.php');
+
+class Cache_Lite_Output extends Cache_Lite
+{
+
+ // --- Public methods ---
+
+ /**
+ * Constructor
+ *
+ * $options is an assoc. To have a look at availables options,
+ * see the constructor of the Cache_Lite class in 'Cache_Lite.php'
+ *
+ * @param array $options options
+ * @access public
+ */
+ function Cache_Lite_Output($options)
+ {
+ $this->Cache_Lite($options);
+ }
+
+ /**
+ * Start the cache
+ *
+ * @param string $id cache id
+ * @param string $group name of the cache group
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return boolean true if the cache is hit (false else)
+ * @access public
+ */
+ function start($id, $group = 'default', $doNotTestCacheValidity = false)
+ {
+ $data = $this->get($id, $group, $doNotTestCacheValidity);
+ if ($data !== false) {
+ echo($data);
+ return true;
+ }
+ ob_start();
+ ob_implicit_flush(false);
+ return false;
+ }
+
+ /**
+ * Stop the cache
+ *
+ * @access public
+ */
+ function end()
+ {
+ $data = ob_get_contents();
+ ob_end_clean();
+ $this->save($data, $this->_id, $this->_group);
+ echo($data);
+ }
+
+}
+
+
+?>
--- /dev/null
+<?php
+/**
+ * Class for performing HTTP requests
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2002-2007, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Alexey Borzov <avb@php.net>
+ * @copyright 2002-2007 Richard Heyes
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Request.php 127 2008-01-17 20:21:37Z dcoulter $
+ * @link http://pear.php.net/package/HTTP_Request/
+ */
+
+/**
+ * PEAR and PEAR_Error classes (for error handling)
+ */
+require_once 'PEAR.php';
+/**
+ * Socket class
+ */
+require_once 'Net/Socket.php';
+/**
+ * URL handling class
+ */
+require_once 'Net/URL.php';
+
+/**#@+
+ * Constants for HTTP request methods
+ */
+define('HTTP_REQUEST_METHOD_GET', 'GET', true);
+define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true);
+define('HTTP_REQUEST_METHOD_POST', 'POST', true);
+define('HTTP_REQUEST_METHOD_PUT', 'PUT', true);
+define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true);
+define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
+define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true);
+/**#@-*/
+
+/**#@+
+ * Constants for HTTP request error codes
+ */
+define('HTTP_REQUEST_ERROR_FILE', 1);
+define('HTTP_REQUEST_ERROR_URL', 2);
+define('HTTP_REQUEST_ERROR_PROXY', 4);
+define('HTTP_REQUEST_ERROR_REDIRECTS', 8);
+define('HTTP_REQUEST_ERROR_RESPONSE', 16);
+define('HTTP_REQUEST_ERROR_GZIP_METHOD', 32);
+define('HTTP_REQUEST_ERROR_GZIP_READ', 64);
+define('HTTP_REQUEST_ERROR_GZIP_DATA', 128);
+define('HTTP_REQUEST_ERROR_GZIP_CRC', 256);
+/**#@-*/
+
+/**#@+
+ * Constants for HTTP protocol versions
+ */
+define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
+define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
+/**#@-*/
+
+if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
+ /**
+ * Whether string functions are overloaded by their mbstring equivalents
+ */
+ define('HTTP_REQUEST_MBSTRING', true);
+} else {
+ /**
+ * @ignore
+ */
+ define('HTTP_REQUEST_MBSTRING', false);
+}
+
+/**
+ * Class for performing HTTP requests
+ *
+ * Simple example (fetches yahoo.com and displays it):
+ * <code>
+ * $a = &new HTTP_Request('http://www.yahoo.com/');
+ * $a->sendRequest();
+ * echo $a->getResponseBody();
+ * </code>
+ *
+ * @category HTTP
+ * @package HTTP_Request
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 1.4.2
+ */
+class HTTP_Request
+{
+ /**#@+
+ * @access private
+ */
+ /**
+ * Instance of Net_URL
+ * @var Net_URL
+ */
+ var $_url;
+
+ /**
+ * Type of request
+ * @var string
+ */
+ var $_method;
+
+ /**
+ * HTTP Version
+ * @var string
+ */
+ var $_http;
+
+ /**
+ * Request headers
+ * @var array
+ */
+ var $_requestHeaders;
+
+ /**
+ * Basic Auth Username
+ * @var string
+ */
+ var $_user;
+
+ /**
+ * Basic Auth Password
+ * @var string
+ */
+ var $_pass;
+
+ /**
+ * Socket object
+ * @var Net_Socket
+ */
+ var $_sock;
+
+ /**
+ * Proxy server
+ * @var string
+ */
+ var $_proxy_host;
+
+ /**
+ * Proxy port
+ * @var integer
+ */
+ var $_proxy_port;
+
+ /**
+ * Proxy username
+ * @var string
+ */
+ var $_proxy_user;
+
+ /**
+ * Proxy password
+ * @var string
+ */
+ var $_proxy_pass;
+
+ /**
+ * Post data
+ * @var array
+ */
+ var $_postData;
+
+ /**
+ * Request body
+ * @var string
+ */
+ var $_body;
+
+ /**
+ * A list of methods that MUST NOT have a request body, per RFC 2616
+ * @var array
+ */
+ var $_bodyDisallowed = array('TRACE');
+
+ /**
+ * Files to post
+ * @var array
+ */
+ var $_postFiles = array();
+
+ /**
+ * Connection timeout.
+ * @var float
+ */
+ var $_timeout;
+
+ /**
+ * HTTP_Response object
+ * @var HTTP_Response
+ */
+ var $_response;
+
+ /**
+ * Whether to allow redirects
+ * @var boolean
+ */
+ var $_allowRedirects;
+
+ /**
+ * Maximum redirects allowed
+ * @var integer
+ */
+ var $_maxRedirects;
+
+ /**
+ * Current number of redirects
+ * @var integer
+ */
+ var $_redirects;
+
+ /**
+ * Whether to append brackets [] to array variables
+ * @var bool
+ */
+ var $_useBrackets = true;
+
+ /**
+ * Attached listeners
+ * @var array
+ */
+ var $_listeners = array();
+
+ /**
+ * Whether to save response body in response object property
+ * @var bool
+ */
+ var $_saveBody = true;
+
+ /**
+ * Timeout for reading from socket (array(seconds, microseconds))
+ * @var array
+ */
+ var $_readTimeout = null;
+
+ /**
+ * Options to pass to Net_Socket::connect. See stream_context_create
+ * @var array
+ */
+ var $_socketOptions = null;
+ /**#@-*/
+
+ /**
+ * Constructor
+ *
+ * Sets up the object
+ * @param string The url to fetch/access
+ * @param array Associative array of parameters which can have the following keys:
+ * <ul>
+ * <li>method - Method to use, GET, POST etc (string)</li>
+ * <li>http - HTTP Version to use, 1.0 or 1.1 (string)</li>
+ * <li>user - Basic Auth username (string)</li>
+ * <li>pass - Basic Auth password (string)</li>
+ * <li>proxy_host - Proxy server host (string)</li>
+ * <li>proxy_port - Proxy server port (integer)</li>
+ * <li>proxy_user - Proxy auth username (string)</li>
+ * <li>proxy_pass - Proxy auth password (string)</li>
+ * <li>timeout - Connection timeout in seconds (float)</li>
+ * <li>allowRedirects - Whether to follow redirects or not (bool)</li>
+ * <li>maxRedirects - Max number of redirects to follow (integer)</li>
+ * <li>useBrackets - Whether to append [] to array variable names (bool)</li>
+ * <li>saveBody - Whether to save response body in response object property (bool)</li>
+ * <li>readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
+ * <li>socketOptions - Options to pass to Net_Socket object (array)</li>
+ * </ul>
+ * @access public
+ */
+ function HTTP_Request($url = '', $params = array())
+ {
+ $this->_method = HTTP_REQUEST_METHOD_GET;
+ $this->_http = HTTP_REQUEST_HTTP_VER_1_1;
+ $this->_requestHeaders = array();
+ $this->_postData = array();
+ $this->_body = null;
+
+ $this->_user = null;
+ $this->_pass = null;
+
+ $this->_proxy_host = null;
+ $this->_proxy_port = null;
+ $this->_proxy_user = null;
+ $this->_proxy_pass = null;
+
+ $this->_allowRedirects = false;
+ $this->_maxRedirects = 3;
+ $this->_redirects = 0;
+
+ $this->_timeout = null;
+ $this->_response = null;
+
+ foreach ($params as $key => $value) {
+ $this->{'_' . $key} = $value;
+ }
+
+ if (!empty($url)) {
+ $this->setURL($url);
+ }
+
+ // Default useragent
+ $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
+
+ // We don't do keep-alives by default
+ $this->addHeader('Connection', 'close');
+
+ // Basic authentication
+ if (!empty($this->_user)) {
+ $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
+ }
+
+ // Proxy authentication (see bug #5913)
+ if (!empty($this->_proxy_user)) {
+ $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
+ }
+
+ // Use gzip encoding if possible
+ if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
+ $this->addHeader('Accept-Encoding', 'gzip');
+ }
+ }
+
+ /**
+ * Generates a Host header for HTTP/1.1 requests
+ *
+ * @access private
+ * @return string
+ */
+ function _generateHostHeader()
+ {
+ if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
+ $host = $this->_url->host . ':' . $this->_url->port;
+
+ } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
+ $host = $this->_url->host . ':' . $this->_url->port;
+
+ } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
+ $host = $this->_url->host . ':' . $this->_url->port;
+
+ } else {
+ $host = $this->_url->host;
+ }
+
+ return $host;
+ }
+
+ /**
+ * Resets the object to its initial state (DEPRECATED).
+ * Takes the same parameters as the constructor.
+ *
+ * @param string $url The url to be requested
+ * @param array $params Associative array of parameters
+ * (see constructor for details)
+ * @access public
+ * @deprecated deprecated since 1.2, call the constructor if this is necessary
+ */
+ function reset($url, $params = array())
+ {
+ $this->HTTP_Request($url, $params);
+ }
+
+ /**
+ * Sets the URL to be requested
+ *
+ * @param string The url to be requested
+ * @access public
+ */
+ function setURL($url)
+ {
+ $this->_url = &new Net_URL($url, $this->_useBrackets);
+
+ if (!empty($this->_url->user) || !empty($this->_url->pass)) {
+ $this->setBasicAuth($this->_url->user, $this->_url->pass);
+ }
+
+ if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
+ $this->addHeader('Host', $this->_generateHostHeader());
+ }
+
+ // set '/' instead of empty path rather than check later (see bug #8662)
+ if (empty($this->_url->path)) {
+ $this->_url->path = '/';
+ }
+ }
+
+ /**
+ * Returns the current request URL
+ *
+ * @return string Current request URL
+ * @access public
+ */
+ function getUrl()
+ {
+ return empty($this->_url)? '': $this->_url->getUrl();
+ }
+
+ /**
+ * Sets a proxy to be used
+ *
+ * @param string Proxy host
+ * @param int Proxy port
+ * @param string Proxy username
+ * @param string Proxy password
+ * @access public
+ */
+ function setProxy($host, $port = 8080, $user = null, $pass = null)
+ {
+ $this->_proxy_host = $host;
+ $this->_proxy_port = $port;
+ $this->_proxy_user = $user;
+ $this->_proxy_pass = $pass;
+
+ if (!empty($user)) {
+ $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
+ }
+ }
+
+ /**
+ * Sets basic authentication parameters
+ *
+ * @param string Username
+ * @param string Password
+ */
+ function setBasicAuth($user, $pass)
+ {
+ $this->_user = $user;
+ $this->_pass = $pass;
+
+ $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
+ }
+
+ /**
+ * Sets the method to be used, GET, POST etc.
+ *
+ * @param string Method to use. Use the defined constants for this
+ * @access public
+ */
+ function setMethod($method)
+ {
+ $this->_method = $method;
+ }
+
+ /**
+ * Sets the HTTP version to use, 1.0 or 1.1
+ *
+ * @param string Version to use. Use the defined constants for this
+ * @access public
+ */
+ function setHttpVer($http)
+ {
+ $this->_http = $http;
+ }
+
+ /**
+ * Adds a request header
+ *
+ * @param string Header name
+ * @param string Header value
+ * @access public
+ */
+ function addHeader($name, $value)
+ {
+ $this->_requestHeaders[strtolower($name)] = $value;
+ }
+
+ /**
+ * Removes a request header
+ *
+ * @param string Header name to remove
+ * @access public
+ */
+ function removeHeader($name)
+ {
+ if (isset($this->_requestHeaders[strtolower($name)])) {
+ unset($this->_requestHeaders[strtolower($name)]);
+ }
+ }
+
+ /**
+ * Adds a querystring parameter
+ *
+ * @param string Querystring parameter name
+ * @param string Querystring parameter value
+ * @param bool Whether the value is already urlencoded or not, default = not
+ * @access public
+ */
+ function addQueryString($name, $value, $preencoded = false)
+ {
+ $this->_url->addQueryString($name, $value, $preencoded);
+ }
+
+ /**
+ * Sets the querystring to literally what you supply
+ *
+ * @param string The querystring data. Should be of the format foo=bar&x=y etc
+ * @param bool Whether data is already urlencoded or not, default = already encoded
+ * @access public
+ */
+ function addRawQueryString($querystring, $preencoded = true)
+ {
+ $this->_url->addRawQueryString($querystring, $preencoded);
+ }
+
+ /**
+ * Adds postdata items
+ *
+ * @param string Post data name
+ * @param string Post data value
+ * @param bool Whether data is already urlencoded or not, default = not
+ * @access public
+ */
+ function addPostData($name, $value, $preencoded = false)
+ {
+ if ($preencoded) {
+ $this->_postData[$name] = $value;
+ } else {
+ $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
+ }
+ }
+
+ /**
+ * Recursively applies the callback function to the value
+ *
+ * @param mixed Callback function
+ * @param mixed Value to process
+ * @access private
+ * @return mixed Processed value
+ */
+ function _arrayMapRecursive($callback, $value)
+ {
+ if (!is_array($value)) {
+ return call_user_func($callback, $value);
+ } else {
+ $map = array();
+ foreach ($value as $k => $v) {
+ $map[$k] = $this->_arrayMapRecursive($callback, $v);
+ }
+ return $map;
+ }
+ }
+
+ /**
+ * Adds a file to upload
+ *
+ * This also changes content-type to 'multipart/form-data' for proper upload
+ *
+ * @access public
+ * @param string name of file-upload field
+ * @param mixed file name(s)
+ * @param mixed content-type(s) of file(s) being uploaded
+ * @return bool true on success
+ * @throws PEAR_Error
+ */
+ function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
+ {
+ if (!is_array($fileName) && !is_readable($fileName)) {
+ return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
+ } elseif (is_array($fileName)) {
+ foreach ($fileName as $name) {
+ if (!is_readable($name)) {
+ return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
+ }
+ }
+ }
+ $this->addHeader('Content-Type', 'multipart/form-data');
+ $this->_postFiles[$inputName] = array(
+ 'name' => $fileName,
+ 'type' => $contentType
+ );
+ return true;
+ }
+
+ /**
+ * Adds raw postdata (DEPRECATED)
+ *
+ * @param string The data
+ * @param bool Whether data is preencoded or not, default = already encoded
+ * @access public
+ * @deprecated deprecated since 1.3.0, method setBody() should be used instead
+ */
+ function addRawPostData($postdata, $preencoded = true)
+ {
+ $this->_body = $preencoded ? $postdata : urlencode($postdata);
+ }
+
+ /**
+ * Sets the request body (for POST, PUT and similar requests)
+ *
+ * @param string Request body
+ * @access public
+ */
+ function setBody($body)
+ {
+ $this->_body = $body;
+ }
+
+ /**
+ * Clears any postdata that has been added (DEPRECATED).
+ *
+ * Useful for multiple request scenarios.
+ *
+ * @access public
+ * @deprecated deprecated since 1.2
+ */
+ function clearPostData()
+ {
+ $this->_postData = null;
+ }
+
+ /**
+ * Appends a cookie to "Cookie:" header
+ *
+ * @param string $name cookie name
+ * @param string $value cookie value
+ * @access public
+ */
+ function addCookie($name, $value)
+ {
+ $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
+ $this->addHeader('Cookie', $cookies . $name . '=' . $value);
+ }
+
+ /**
+ * Clears any cookies that have been added (DEPRECATED).
+ *
+ * Useful for multiple request scenarios
+ *
+ * @access public
+ * @deprecated deprecated since 1.2
+ */
+ function clearCookies()
+ {
+ $this->removeHeader('Cookie');
+ }
+
+ /**
+ * Sends the request
+ *
+ * @access public
+ * @param bool Whether to store response body in Response object property,
+ * set this to false if downloading a LARGE file and using a Listener
+ * @return mixed PEAR error on error, true otherwise
+ */
+ function sendRequest($saveBody = true)
+ {
+ if (!is_a($this->_url, 'Net_URL')) {
+ return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
+ }
+
+ $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
+ $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
+
+ // 4.3.0 supports SSL connections using OpenSSL. The function test determines
+ // we running on at least 4.3.0
+ if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) {
+ if (isset($this->_proxy_host)) {
+ return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
+ }
+ $host = 'ssl://' . $host;
+ }
+
+ // magic quotes may fuck up file uploads and chunked response processing
+ $magicQuotes = ini_get('magic_quotes_runtime');
+ ini_set('magic_quotes_runtime', false);
+
+ // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
+ // connection token to a proxy server...
+ if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
+ 'Keep-Alive' == $this->_requestHeaders['connection'])
+ {
+ $this->removeHeader('connection');
+ }
+
+ $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
+ (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
+ $sockets = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
+ $sockKey = $host . ':' . $port;
+ unset($this->_sock);
+
+ // There is a connected socket in the "static" property?
+ if ($keepAlive && !empty($sockets[$sockKey]) &&
+ !empty($sockets[$sockKey]->fp))
+ {
+ $this->_sock =& $sockets[$sockKey];
+ $err = null;
+ } else {
+ $this->_notify('connect');
+ $this->_sock =& new Net_Socket();
+ $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
+ }
+ PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
+
+ if (!PEAR::isError($err)) {
+ if (!empty($this->_readTimeout)) {
+ $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
+ }
+
+ $this->_notify('sentRequest');
+
+ // Read the response
+ $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
+ $err = $this->_response->process(
+ $this->_saveBody && $saveBody,
+ HTTP_REQUEST_METHOD_HEAD != $this->_method
+ );
+
+ if ($keepAlive) {
+ $keepAlive = (isset($this->_response->_headers['content-length'])
+ || (isset($this->_response->_headers['transfer-encoding'])
+ && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
+ if ($keepAlive) {
+ if (isset($this->_response->_headers['connection'])) {
+ $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
+ } else {
+ $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
+ }
+ }
+ }
+ }
+
+ ini_set('magic_quotes_runtime', $magicQuotes);
+
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+
+ if (!$keepAlive) {
+ $this->disconnect();
+ // Store the connected socket in "static" property
+ } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
+ $sockets[$sockKey] =& $this->_sock;
+ }
+
+ // Check for redirection
+ if ( $this->_allowRedirects
+ AND $this->_redirects <= $this->_maxRedirects
+ AND $this->getResponseCode() > 300
+ AND $this->getResponseCode() < 399
+ AND !empty($this->_response->_headers['location'])) {
+
+
+ $redirect = $this->_response->_headers['location'];
+
+ // Absolute URL
+ if (preg_match('/^https?:\/\//i', $redirect)) {
+ $this->_url = &new Net_URL($redirect);
+ $this->addHeader('Host', $this->_generateHostHeader());
+ // Absolute path
+ } elseif ($redirect{0} == '/') {
+ $this->_url->path = $redirect;
+
+ // Relative path
+ } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
+ if (substr($this->_url->path, -1) == '/') {
+ $redirect = $this->_url->path . $redirect;
+ } else {
+ $redirect = dirname($this->_url->path) . '/' . $redirect;
+ }
+ $redirect = Net_URL::resolvePath($redirect);
+ $this->_url->path = $redirect;
+
+ // Filename, no path
+ } else {
+ if (substr($this->_url->path, -1) == '/') {
+ $redirect = $this->_url->path . $redirect;
+ } else {
+ $redirect = dirname($this->_url->path) . '/' . $redirect;
+ }
+ $this->_url->path = $redirect;
+ }
+
+ $this->_redirects++;
+ return $this->sendRequest($saveBody);
+
+ // Too many redirects
+ } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
+ return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
+ }
+
+ return true;
+ }
+
+ /**
+ * Disconnect the socket, if connected. Only useful if using Keep-Alive.
+ *
+ * @access public
+ */
+ function disconnect()
+ {
+ if (!empty($this->_sock) && !empty($this->_sock->fp)) {
+ $this->_notify('disconnect');
+ $this->_sock->disconnect();
+ }
+ }
+
+ /**
+ * Returns the response code
+ *
+ * @access public
+ * @return mixed Response code, false if not set
+ */
+ function getResponseCode()
+ {
+ return isset($this->_response->_code) ? $this->_response->_code : false;
+ }
+
+ /**
+ * Returns either the named header or all if no name given
+ *
+ * @access public
+ * @param string The header name to return, do not set to get all headers
+ * @return mixed either the value of $headername (false if header is not present)
+ * or an array of all headers
+ */
+ function getResponseHeader($headername = null)
+ {
+ if (!isset($headername)) {
+ return isset($this->_response->_headers)? $this->_response->_headers: array();
+ } else {
+ $headername = strtolower($headername);
+ return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
+ }
+ }
+
+ /**
+ * Returns the body of the response
+ *
+ * @access public
+ * @return mixed response body, false if not set
+ */
+ function getResponseBody()
+ {
+ return isset($this->_response->_body) ? $this->_response->_body : false;
+ }
+
+ /**
+ * Returns cookies set in response
+ *
+ * @access public
+ * @return mixed array of response cookies, false if none are present
+ */
+ function getResponseCookies()
+ {
+ return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
+ }
+
+ /**
+ * Builds the request string
+ *
+ * @access private
+ * @return string The request string
+ */
+ function _buildRequest()
+ {
+ $separator = ini_get('arg_separator.output');
+ ini_set('arg_separator.output', '&');
+ $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
+ ini_set('arg_separator.output', $separator);
+
+ $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
+ $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
+ $path = $this->_url->path . $querystring;
+ $url = $host . $port . $path;
+
+ if (!strlen($url)) {
+ $url = '/';
+ }
+
+ $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
+
+ if (in_array($this->_method, $this->_bodyDisallowed) ||
+ (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
+ (empty($this->_postData) && empty($this->_postFiles)))))
+ {
+ $this->removeHeader('Content-Type');
+ } else {
+ if (empty($this->_requestHeaders['content-type'])) {
+ // Add default content-type
+ $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
+ } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
+ $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
+ $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
+ }
+ }
+
+ // Request Headers
+ if (!empty($this->_requestHeaders)) {
+ foreach ($this->_requestHeaders as $name => $value) {
+ $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
+ $request .= $canonicalName . ': ' . $value . "\r\n";
+ }
+ }
+
+ // No post data or wrong method, so simply add a final CRLF
+ if (in_array($this->_method, $this->_bodyDisallowed) ||
+ (HTTP_REQUEST_METHOD_POST != $this->_method && 0 == strlen($this->_body))) {
+
+ $request .= "\r\n";
+
+ // Post data if it's an array
+ } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&
+ (!empty($this->_postData) || !empty($this->_postFiles))) {
+
+ // "normal" POST request
+ if (!isset($boundary)) {
+ $postdata = implode('&', array_map(
+ create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
+ $this->_flattenArray('', $this->_postData)
+ ));
+
+ // multipart request, probably with file uploads
+ } else {
+ $postdata = '';
+ if (!empty($this->_postData)) {
+ $flatData = $this->_flattenArray('', $this->_postData);
+ foreach ($flatData as $item) {
+ $postdata .= '--' . $boundary . "\r\n";
+ $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
+ $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
+ }
+ }
+ foreach ($this->_postFiles as $name => $value) {
+ if (is_array($value['name'])) {
+ $varname = $name . ($this->_useBrackets? '[]': '');
+ } else {
+ $varname = $name;
+ $value['name'] = array($value['name']);
+ }
+ foreach ($value['name'] as $key => $filename) {
+ $fp = fopen($filename, 'r');
+ $data = fread($fp, filesize($filename));
+ fclose($fp);
+ $basename = basename($filename);
+ $type = is_array($value['type'])? @$value['type'][$key]: $value['type'];
+
+ $postdata .= '--' . $boundary . "\r\n";
+ $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
+ $postdata .= "\r\nContent-Type: " . $type;
+ $postdata .= "\r\n\r\n" . $data . "\r\n";
+ }
+ }
+ $postdata .= '--' . $boundary . "--\r\n";
+ }
+ $request .= 'Content-Length: ' .
+ (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
+ "\r\n\r\n";
+ $request .= $postdata;
+
+ // Explicitly set request body
+ } elseif (0 < strlen($this->_body)) {
+
+ $request .= 'Content-Length: ' .
+ (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
+ "\r\n\r\n";
+ $request .= $this->_body;
+
+ // Terminate headers with CRLF on POST request with no body, too
+ } else {
+
+ $request .= "\r\n";
+ }
+
+ return $request;
+ }
+
+ /**
+ * Helper function to change the (probably multidimensional) associative array
+ * into the simple one.
+ *
+ * @param string name for item
+ * @param mixed item's values
+ * @return array array with the following items: array('item name', 'item value');
+ * @access private
+ */
+ function _flattenArray($name, $values)
+ {
+ if (!is_array($values)) {
+ return array(array($name, $values));
+ } else {
+ $ret = array();
+ foreach ($values as $k => $v) {
+ if (empty($name)) {
+ $newName = $k;
+ } elseif ($this->_useBrackets) {
+ $newName = $name . '[' . $k . ']';
+ } else {
+ $newName = $name;
+ }
+ $ret = array_merge($ret, $this->_flattenArray($newName, $v));
+ }
+ return $ret;
+ }
+ }
+
+
+ /**
+ * Adds a Listener to the list of listeners that are notified of
+ * the object's events
+ *
+ * Events sent by HTTP_Request object
+ * - 'connect': on connection to server
+ * - 'sentRequest': after the request was sent
+ * - 'disconnect': on disconnection from server
+ *
+ * Events sent by HTTP_Response object
+ * - 'gotHeaders': after receiving response headers (headers are passed in $data)
+ * - 'tick': on receiving a part of response body (the part is passed in $data)
+ * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
+ * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
+ *
+ * @param HTTP_Request_Listener listener to attach
+ * @return boolean whether the listener was successfully attached
+ * @access public
+ */
+ function attach(&$listener)
+ {
+ if (!is_a($listener, 'HTTP_Request_Listener')) {
+ return false;
+ }
+ $this->_listeners[$listener->getId()] =& $listener;
+ return true;
+ }
+
+
+ /**
+ * Removes a Listener from the list of listeners
+ *
+ * @param HTTP_Request_Listener listener to detach
+ * @return boolean whether the listener was successfully detached
+ * @access public
+ */
+ function detach(&$listener)
+ {
+ if (!is_a($listener, 'HTTP_Request_Listener') ||
+ !isset($this->_listeners[$listener->getId()])) {
+ return false;
+ }
+ unset($this->_listeners[$listener->getId()]);
+ return true;
+ }
+
+
+ /**
+ * Notifies all registered listeners of an event.
+ *
+ * @param string Event name
+ * @param mixed Additional data
+ * @access private
+ * @see HTTP_Request::attach()
+ */
+ function _notify($event, $data = null)
+ {
+ foreach (array_keys($this->_listeners) as $id) {
+ $this->_listeners[$id]->update($this, $event, $data);
+ }
+ }
+}
+
+
+/**
+ * Response class to complement the Request class
+ *
+ * @category HTTP
+ * @package HTTP_Request
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 1.4.2
+ */
+class HTTP_Response
+{
+ /**
+ * Socket object
+ * @var Net_Socket
+ */
+ var $_sock;
+
+ /**
+ * Protocol
+ * @var string
+ */
+ var $_protocol;
+
+ /**
+ * Return code
+ * @var string
+ */
+ var $_code;
+
+ /**
+ * Response headers
+ * @var array
+ */
+ var $_headers;
+
+ /**
+ * Cookies set in response
+ * @var array
+ */
+ var $_cookies;
+
+ /**
+ * Response body
+ * @var string
+ */
+ var $_body = '';
+
+ /**
+ * Used by _readChunked(): remaining length of the current chunk
+ * @var string
+ */
+ var $_chunkLength = 0;
+
+ /**
+ * Attached listeners
+ * @var array
+ */
+ var $_listeners = array();
+
+ /**
+ * Bytes left to read from message-body
+ * @var null|int
+ */
+ var $_toRead;
+
+ /**
+ * Constructor
+ *
+ * @param Net_Socket socket to read the response from
+ * @param array listeners attached to request
+ */
+ function HTTP_Response(&$sock, &$listeners)
+ {
+ $this->_sock =& $sock;
+ $this->_listeners =& $listeners;
+ }
+
+
+ /**
+ * Processes a HTTP response
+ *
+ * This extracts response code, headers, cookies and decodes body if it
+ * was encoded in some way
+ *
+ * @access public
+ * @param bool Whether to store response body in object property, set
+ * this to false if downloading a LARGE file and using a Listener.
+ * This is assumed to be true if body is gzip-encoded.
+ * @param bool Whether the response can actually have a message-body.
+ * Will be set to false for HEAD requests.
+ * @throws PEAR_Error
+ * @return mixed true on success, PEAR_Error in case of malformed response
+ */
+ function process($saveBody = true, $canHaveBody = true)
+ {
+ do {
+ $line = $this->_sock->readLine();
+ if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) {
+ return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
+ } else {
+ $this->_protocol = 'HTTP/' . $http_version;
+ $this->_code = intval($returncode);
+ }
+ while ('' !== ($header = $this->_sock->readLine())) {
+ $this->_processHeader($header);
+ }
+ } while (100 == $this->_code);
+
+ $this->_notify('gotHeaders', $this->_headers);
+
+ // RFC 2616, section 4.4:
+ // 1. Any response message which "MUST NOT" include a message-body ...
+ // is always terminated by the first empty line after the header fields
+ // 3. ... If a message is received with both a
+ // Transfer-Encoding header field and a Content-Length header field,
+ // the latter MUST be ignored.
+ $canHaveBody = $canHaveBody && $this->_code >= 200 &&
+ $this->_code != 204 && $this->_code != 304;
+
+ // If response body is present, read it and decode
+ $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
+ $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
+ $hasBody = false;
+ if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||
+ 0 != $this->_headers['content-length']))
+ {
+ if ($chunked || !isset($this->_headers['content-length'])) {
+ $this->_toRead = null;
+ } else {
+ $this->_toRead = $this->_headers['content-length'];
+ }
+ while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
+ if ($chunked) {
+ $data = $this->_readChunked();
+ } elseif (is_null($this->_toRead)) {
+ $data = $this->_sock->read(4096);
+ } else {
+ $data = $this->_sock->read(min(4096, $this->_toRead));
+ $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
+ }
+ if ('' == $data) {
+ break;
+ } else {
+ $hasBody = true;
+ if ($saveBody || $gzipped) {
+ $this->_body .= $data;
+ }
+ $this->_notify($gzipped? 'gzTick': 'tick', $data);
+ }
+ }
+ }
+
+ if ($hasBody) {
+ // Uncompress the body if needed
+ if ($gzipped) {
+ $body = $this->_decodeGzip($this->_body);
+ if (PEAR::isError($body)) {
+ return $body;
+ }
+ $this->_body = $body;
+ $this->_notify('gotBody', $this->_body);
+ } else {
+ $this->_notify('gotBody');
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Processes the response header
+ *
+ * @access private
+ * @param string HTTP header
+ */
+ function _processHeader($header)
+ {
+ if (false === strpos($header, ':')) {
+ return;
+ }
+ list($headername, $headervalue) = explode(':', $header, 2);
+ $headername = strtolower($headername);
+ $headervalue = ltrim($headervalue);
+
+ if ('set-cookie' != $headername) {
+ if (isset($this->_headers[$headername])) {
+ $this->_headers[$headername] .= ',' . $headervalue;
+ } else {
+ $this->_headers[$headername] = $headervalue;
+ }
+ } else {
+ $this->_parseCookie($headervalue);
+ }
+ }
+
+
+ /**
+ * Parse a Set-Cookie header to fill $_cookies array
+ *
+ * @access private
+ * @param string value of Set-Cookie header
+ */
+ function _parseCookie($headervalue)
+ {
+ $cookie = array(
+ 'expires' => null,
+ 'domain' => null,
+ 'path' => null,
+ 'secure' => false
+ );
+
+ // Only a name=value pair
+ if (!strpos($headervalue, ';')) {
+ $pos = strpos($headervalue, '=');
+ $cookie['name'] = trim(substr($headervalue, 0, $pos));
+ $cookie['value'] = trim(substr($headervalue, $pos + 1));
+
+ // Some optional parameters are supplied
+ } else {
+ $elements = explode(';', $headervalue);
+ $pos = strpos($elements[0], '=');
+ $cookie['name'] = trim(substr($elements[0], 0, $pos));
+ $cookie['value'] = trim(substr($elements[0], $pos + 1));
+
+ for ($i = 1; $i < count($elements); $i++) {
+ if (false === strpos($elements[$i], '=')) {
+ $elName = trim($elements[$i]);
+ $elValue = null;
+ } else {
+ list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
+ }
+ $elName = strtolower($elName);
+ if ('secure' == $elName) {
+ $cookie['secure'] = true;
+ } elseif ('expires' == $elName) {
+ $cookie['expires'] = str_replace('"', '', $elValue);
+ } elseif ('path' == $elName || 'domain' == $elName) {
+ $cookie[$elName] = urldecode($elValue);
+ } else {
+ $cookie[$elName] = $elValue;
+ }
+ }
+ }
+ $this->_cookies[] = $cookie;
+ }
+
+
+ /**
+ * Read a part of response body encoded with chunked Transfer-Encoding
+ *
+ * @access private
+ * @return string
+ */
+ function _readChunked()
+ {
+ // at start of the next chunk?
+ if (0 == $this->_chunkLength) {
+ $line = $this->_sock->readLine();
+ if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
+ $this->_chunkLength = hexdec($matches[1]);
+ // Chunk with zero length indicates the end
+ if (0 == $this->_chunkLength) {
+ $this->_sock->readLine(); // make this an eof()
+ return '';
+ }
+ } else {
+ return '';
+ }
+ }
+ $data = $this->_sock->read($this->_chunkLength);
+ $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
+ if (0 == $this->_chunkLength) {
+ $this->_sock->readLine(); // Trailing CRLF
+ }
+ return $data;
+ }
+
+
+ /**
+ * Notifies all registered listeners of an event.
+ *
+ * @param string Event name
+ * @param mixed Additional data
+ * @access private
+ * @see HTTP_Request::_notify()
+ */
+ function _notify($event, $data = null)
+ {
+ foreach (array_keys($this->_listeners) as $id) {
+ $this->_listeners[$id]->update($this, $event, $data);
+ }
+ }
+
+
+ /**
+ * Decodes the message-body encoded by gzip
+ *
+ * The real decoding work is done by gzinflate() built-in function, this
+ * method only parses the header and checks data for compliance with
+ * RFC 1952
+ *
+ * @access private
+ * @param string gzip-encoded data
+ * @return string decoded data
+ */
+ function _decodeGzip($data)
+ {
+ if (HTTP_REQUEST_MBSTRING) {
+ $oldEncoding = mb_internal_encoding();
+ mb_internal_encoding('iso-8859-1');
+ }
+ $length = strlen($data);
+ // If it doesn't look like gzip-encoded data, don't bother
+ if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
+ return $data;
+ }
+ $method = ord(substr($data, 2, 1));
+ if (8 != $method) {
+ return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
+ }
+ $flags = ord(substr($data, 3, 1));
+ if ($flags & 224) {
+ return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+
+ // header is 10 bytes minimum. may be longer, though.
+ $headerLength = 10;
+ // extra fields, need to skip 'em
+ if ($flags & 4) {
+ if ($length - $headerLength - 2 < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $extraLength = unpack('v', substr($data, 10, 2));
+ if ($length - $headerLength - 2 - $extraLength[1] < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $headerLength += $extraLength[1] + 2;
+ }
+ // file name, need to skip that
+ if ($flags & 8) {
+ if ($length - $headerLength - 1 < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $filenameLength = strpos(substr($data, $headerLength), chr(0));
+ if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $headerLength += $filenameLength + 1;
+ }
+ // comment, need to skip that also
+ if ($flags & 16) {
+ if ($length - $headerLength - 1 < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $commentLength = strpos(substr($data, $headerLength), chr(0));
+ if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $headerLength += $commentLength + 1;
+ }
+ // have a CRC for header. let's check
+ if ($flags & 1) {
+ if ($length - $headerLength - 2 < 8) {
+ return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
+ }
+ $crcReal = 0xffff & crc32(substr($data, 0, $headerLength));
+ $crcStored = unpack('v', substr($data, $headerLength, 2));
+ if ($crcReal != $crcStored[1]) {
+ return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
+ }
+ $headerLength += 2;
+ }
+ // unpacked data CRC and size at the end of encoded data
+ $tmp = unpack('V2', substr($data, -8));
+ $dataCrc = $tmp[1];
+ $dataSize = $tmp[2];
+
+ // finally, call the gzinflate() function
+ $unpacked = @gzinflate(substr($data, $headerLength, -8), $dataSize);
+ if (false === $unpacked) {
+ return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);
+ } elseif ($dataSize != strlen($unpacked)) {
+ return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);
+ } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
+ return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
+ }
+ if (HTTP_REQUEST_MBSTRING) {
+ mb_internal_encoding($oldEncoding);
+ }
+ return $unpacked;
+ }
+} // End class HTTP_Response
+?>
--- /dev/null
+<?php
+/**
+ * Listener for HTTP_Request and HTTP_Response objects
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2002-2007, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request
+ * @author Alexey Borzov <avb@php.net>
+ * @copyright 2002-2007 Richard Heyes
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Listener.php 127 2008-01-17 20:21:37Z dcoulter $
+ * @link http://pear.php.net/package/HTTP_Request/
+ */
+
+/**
+ * Listener for HTTP_Request and HTTP_Response objects
+ *
+ * This class implements the Observer part of a Subject-Observer
+ * design pattern.
+ *
+ * @category HTTP
+ * @package HTTP_Request
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 1.4.2
+ */
+class HTTP_Request_Listener
+{
+ /**
+ * A listener's identifier
+ * @var string
+ */
+ var $_id;
+
+ /**
+ * Constructor, sets the object's identifier
+ *
+ * @access public
+ */
+ function HTTP_Request_Listener()
+ {
+ $this->_id = md5(uniqid('http_request_', 1));
+ }
+
+
+ /**
+ * Returns the listener's identifier
+ *
+ * @access public
+ * @return string
+ */
+ function getId()
+ {
+ return $this->_id;
+ }
+
+
+ /**
+ * This method is called when Listener is notified of an event
+ *
+ * @access public
+ * @param object an object the listener is attached to
+ * @param string Event name
+ * @param mixed Additional data
+ * @abstract
+ */
+ function update(&$subject, $event, $data = null)
+ {
+ echo "Notified of event: '$event'\n";
+ if (null !== $data) {
+ echo "Additional data: ";
+ var_dump($data);
+ }
+ }
+}
+?>
--- /dev/null
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Stig Bakken <ssb@php.net> |
+// | Chuck Hagenbuch <chuck@horde.org> |
+// +----------------------------------------------------------------------+
+//
+// $Id: Socket.php 32 2005-08-01 06:21:02Z dancoulter $
+
+require_once 'PEAR.php';
+
+define('NET_SOCKET_READ', 1);
+define('NET_SOCKET_WRITE', 2);
+define('NET_SOCKET_ERROR', 3);
+
+/**
+ * Generalized Socket class.
+ *
+ * @version 1.1
+ * @author Stig Bakken <ssb@php.net>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+class Net_Socket extends PEAR {
+
+ /**
+ * Socket file pointer.
+ * @var resource $fp
+ */
+ var $fp = null;
+
+ /**
+ * Whether the socket is blocking. Defaults to true.
+ * @var boolean $blocking
+ */
+ var $blocking = true;
+
+ /**
+ * Whether the socket is persistent. Defaults to false.
+ * @var boolean $persistent
+ */
+ var $persistent = false;
+
+ /**
+ * The IP address to connect to.
+ * @var string $addr
+ */
+ var $addr = '';
+
+ /**
+ * The port number to connect to.
+ * @var integer $port
+ */
+ var $port = 0;
+
+ /**
+ * Number of seconds to wait on socket connections before assuming
+ * there's no more data. Defaults to no timeout.
+ * @var integer $timeout
+ */
+ var $timeout = false;
+
+ /**
+ * Number of bytes to read at a time in readLine() and
+ * readAll(). Defaults to 2048.
+ * @var integer $lineLength
+ */
+ var $lineLength = 2048;
+
+ /**
+ * Connect to the specified port. If called when the socket is
+ * already connected, it disconnects and connects again.
+ *
+ * @param string $addr IP address or host name.
+ * @param integer $port TCP port number.
+ * @param boolean $persistent (optional) Whether the connection is
+ * persistent (kept open between requests
+ * by the web server).
+ * @param integer $timeout (optional) How long to wait for data.
+ * @param array $options See options for stream_context_create.
+ *
+ * @access public
+ *
+ * @return boolean | PEAR_Error True on success or a PEAR_Error on failure.
+ */
+ function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null)
+ {
+ if (is_resource($this->fp)) {
+ @fclose($this->fp);
+ $this->fp = null;
+ }
+
+ if (!$addr) {
+ return $this->raiseError('$addr cannot be empty');
+ } elseif (strspn($addr, '.0123456789') == strlen($addr) ||
+ strstr($addr, '/') !== false) {
+ $this->addr = $addr;
+ } else {
+ $this->addr = @gethostbyname($addr);
+ }
+
+ $this->port = $port % 65536;
+
+ if ($persistent !== null) {
+ $this->persistent = $persistent;
+ }
+
+ if ($timeout !== null) {
+ $this->timeout = $timeout;
+ }
+
+ $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
+ $errno = 0;
+ $errstr = '';
+ if ($options && function_exists('stream_context_create')) {
+ if ($this->timeout) {
+ $timeout = $this->timeout;
+ } else {
+ $timeout = 0;
+ }
+ $context = stream_context_create($options);
+ $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context);
+ } else {
+ if ($this->timeout) {
+ $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
+ } else {
+ $fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
+ }
+ }
+
+ if (!$fp) {
+ return $this->raiseError($errstr, $errno);
+ }
+
+ $this->fp = $fp;
+
+ return $this->setBlocking($this->blocking);
+ }
+
+ /**
+ * Disconnects from the peer, closes the socket.
+ *
+ * @access public
+ * @return mixed true on success or an error object otherwise
+ */
+ function disconnect()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ @fclose($this->fp);
+ $this->fp = null;
+ return true;
+ }
+
+ /**
+ * Find out if the socket is in blocking mode.
+ *
+ * @access public
+ * @return boolean The current blocking mode.
+ */
+ function isBlocking()
+ {
+ return $this->blocking;
+ }
+
+ /**
+ * Sets whether the socket connection should be blocking or
+ * not. A read call to a non-blocking socket will return immediately
+ * if there is no data available, whereas it will block until there
+ * is data for blocking sockets.
+ *
+ * @param boolean $mode True for blocking sockets, false for nonblocking.
+ * @access public
+ * @return mixed true on success or an error object otherwise
+ */
+ function setBlocking($mode)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $this->blocking = $mode;
+ socket_set_blocking($this->fp, $this->blocking);
+ return true;
+ }
+
+ /**
+ * Sets the timeout value on socket descriptor,
+ * expressed in the sum of seconds and microseconds
+ *
+ * @param integer $seconds Seconds.
+ * @param integer $microseconds Microseconds.
+ * @access public
+ * @return mixed true on success or an error object otherwise
+ */
+ function setTimeout($seconds, $microseconds)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return socket_set_timeout($this->fp, $seconds, $microseconds);
+ }
+
+ /**
+ * Returns information about an existing socket resource.
+ * Currently returns four entries in the result array:
+ *
+ * <p>
+ * timed_out (bool) - The socket timed out waiting for data<br>
+ * blocked (bool) - The socket was blocked<br>
+ * eof (bool) - Indicates EOF event<br>
+ * unread_bytes (int) - Number of bytes left in the socket buffer<br>
+ * </p>
+ *
+ * @access public
+ * @return mixed Array containing information about existing socket resource or an error object otherwise
+ */
+ function getStatus()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return socket_get_status($this->fp);
+ }
+
+ /**
+ * Get a specified line of data
+ *
+ * @access public
+ * @return $size bytes of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function gets($size)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return @fgets($this->fp, $size);
+ }
+
+ /**
+ * Read a specified amount of data. This is guaranteed to return,
+ * and has the added benefit of getting everything in one fread()
+ * chunk; if you know the size of the data you're getting
+ * beforehand, this is definitely the way to go.
+ *
+ * @param integer $size The number of bytes to read from the socket.
+ * @access public
+ * @return $size bytes of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function read($size)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return @fread($this->fp, $size);
+ }
+
+ /**
+ * Write a specified amount of data.
+ *
+ * @param string $data Data to write.
+ * @param integer $blocksize Amount of data to write at once.
+ * NULL means all at once.
+ *
+ * @access public
+ * @return mixed true on success or an error object otherwise
+ */
+ function write($data, $blocksize = null)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ if (is_null($blocksize) && !OS_WINDOWS) {
+ return fwrite($this->fp, $data);
+ } else {
+ if (is_null($blocksize)) {
+ $blocksize = 1024;
+ }
+
+ $pos = 0;
+ $size = strlen($data);
+ while ($pos < $size) {
+ $written = @fwrite($this->fp, substr($data, $pos, $blocksize));
+ if ($written === false) {
+ return false;
+ }
+ $pos += $written;
+ }
+
+ return $pos;
+ }
+ }
+
+ /**
+ * Write a line of data to the socket, followed by a trailing "\r\n".
+ *
+ * @access public
+ * @return mixed fputs result, or an error
+ */
+ function writeLine($data)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return fwrite($this->fp, $data . "\r\n");
+ }
+
+ /**
+ * Tests for end-of-file on a socket descriptor.
+ *
+ * @access public
+ * @return bool
+ */
+ function eof()
+ {
+ return (is_resource($this->fp) && feof($this->fp));
+ }
+
+ /**
+ * Reads a byte of data
+ *
+ * @access public
+ * @return 1 byte of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readByte()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return ord(@fread($this->fp, 1));
+ }
+
+ /**
+ * Reads a word of data
+ *
+ * @access public
+ * @return 1 word of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readWord()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $buf = @fread($this->fp, 2);
+ return (ord($buf[0]) + (ord($buf[1]) << 8));
+ }
+
+ /**
+ * Reads an int of data
+ *
+ * @access public
+ * @return integer 1 int of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readInt()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $buf = @fread($this->fp, 4);
+ return (ord($buf[0]) + (ord($buf[1]) << 8) +
+ (ord($buf[2]) << 16) + (ord($buf[3]) << 24));
+ }
+
+ /**
+ * Reads a zero-terminated string of data
+ *
+ * @access public
+ * @return string, or a PEAR_Error if
+ * not connected.
+ */
+ function readString()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $string = '';
+ while (($char = @fread($this->fp, 1)) != "\x00") {
+ $string .= $char;
+ }
+ return $string;
+ }
+
+ /**
+ * Reads an IP Address and returns it in a dot formated string
+ *
+ * @access public
+ * @return Dot formated string, or a PEAR_Error if
+ * not connected.
+ */
+ function readIPAddress()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $buf = @fread($this->fp, 4);
+ return sprintf("%s.%s.%s.%s", ord($buf[0]), ord($buf[1]),
+ ord($buf[2]), ord($buf[3]));
+ }
+
+ /**
+ * Read until either the end of the socket or a newline, whichever
+ * comes first. Strips the trailing newline from the returned data.
+ *
+ * @access public
+ * @return All available data up to a newline, without that
+ * newline, or until the end of the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readLine()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $line = '';
+ $timeout = time() + $this->timeout;
+ while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
+ $line .= @fgets($this->fp, $this->lineLength);
+ if (substr($line, -1) == "\n") {
+ return rtrim($line, "\r\n");
+ }
+ }
+ return $line;
+ }
+
+ /**
+ * Read until the socket closes, or until there is no more data in
+ * the inner PHP buffer. If the inner buffer is empty, in blocking
+ * mode we wait for at least 1 byte of data. Therefore, in
+ * blocking mode, if there is no data at all to be read, this
+ * function will never exit (unless the socket is closed on the
+ * remote end).
+ *
+ * @access public
+ *
+ * @return string All data until the socket closes, or a PEAR_Error if
+ * not connected.
+ */
+ function readAll()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $data = '';
+ while (!feof($this->fp)) {
+ $data .= @fread($this->fp, $this->lineLength);
+ }
+ return $data;
+ }
+
+ /**
+ * Runs the equivalent of the select() system call on the socket
+ * with a timeout specified by tv_sec and tv_usec.
+ *
+ * @param integer $state Which of read/write/error to check for.
+ * @param integer $tv_sec Number of seconds for timeout.
+ * @param integer $tv_usec Number of microseconds for timeout.
+ *
+ * @access public
+ * @return False if select fails, integer describing which of read/write/error
+ * are ready, or PEAR_Error if not connected.
+ */
+ function select($state, $tv_sec, $tv_usec = 0)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $read = null;
+ $write = null;
+ $except = null;
+ if ($state & NET_SOCKET_READ) {
+ $read[] = $this->fp;
+ }
+ if ($state & NET_SOCKET_WRITE) {
+ $write[] = $this->fp;
+ }
+ if ($state & NET_SOCKET_ERROR) {
+ $except[] = $this->fp;
+ }
+ if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) {
+ return false;
+ }
+
+ $result = 0;
+ if (count($read)) {
+ $result |= NET_SOCKET_READ;
+ }
+ if (count($write)) {
+ $result |= NET_SOCKET_WRITE;
+ }
+ if (count($except)) {
+ $result |= NET_SOCKET_ERROR;
+ }
+ return $result;
+ }
+
+}
--- /dev/null
+<?php
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2002-2004, Richard Heyes |
+// | All rights reserved. |
+// | |
+// | Redistribution and use in source and binary forms, with or without |
+// | modification, are permitted provided that the following conditions |
+// | are met: |
+// | |
+// | o Redistributions of source code must retain the above copyright |
+// | notice, this list of conditions and the following disclaimer. |
+// | o Redistributions in binary form must reproduce the above copyright |
+// | notice, this list of conditions and the following disclaimer in the |
+// | documentation and/or other materials provided with the distribution.|
+// | o The names of the authors may not be used to endorse or promote |
+// | products derived from this software without specific prior written |
+// | permission. |
+// | |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+// | |
+// +-----------------------------------------------------------------------+
+// | Author: Richard Heyes <richard at php net> |
+// +-----------------------------------------------------------------------+
+//
+// $Id: URL.php 32 2005-08-01 06:21:02Z dancoulter $
+//
+// Net_URL Class
+
+class Net_URL
+{
+ /**
+ * Full url
+ * @var string
+ */
+ var $url;
+
+ /**
+ * Protocol
+ * @var string
+ */
+ var $protocol;
+
+ /**
+ * Username
+ * @var string
+ */
+ var $username;
+
+ /**
+ * Password
+ * @var string
+ */
+ var $password;
+
+ /**
+ * Host
+ * @var string
+ */
+ var $host;
+
+ /**
+ * Port
+ * @var integer
+ */
+ var $port;
+
+ /**
+ * Path
+ * @var string
+ */
+ var $path;
+
+ /**
+ * Query string
+ * @var array
+ */
+ var $querystring;
+
+ /**
+ * Anchor
+ * @var string
+ */
+ var $anchor;
+
+ /**
+ * Whether to use []
+ * @var bool
+ */
+ var $useBrackets;
+
+ /**
+ * PHP4 Constructor
+ *
+ * @see __construct()
+ */
+ function Net_URL($url = null, $useBrackets = true)
+ {
+ $this->__construct($url, $useBrackets);
+ }
+
+ /**
+ * PHP5 Constructor
+ *
+ * Parses the given url and stores the various parts
+ * Defaults are used in certain cases
+ *
+ * @param string $url Optional URL
+ * @param bool $useBrackets Whether to use square brackets when
+ * multiple querystrings with the same name
+ * exist
+ */
+ function __construct($url = null, $useBrackets = true)
+ {
+ $HTTP_SERVER_VARS = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS'];
+
+ $this->useBrackets = $useBrackets;
+ $this->url = $url;
+ $this->user = '';
+ $this->pass = '';
+ $this->host = '';
+ $this->port = 80;
+ $this->path = '';
+ $this->querystring = array();
+ $this->anchor = '';
+
+ // Only use defaults if not an absolute URL given
+ if (!preg_match('/^[a-z0-9]+:\/\//i', $url)) {
+
+ $this->protocol = (@$HTTP_SERVER_VARS['HTTPS'] == 'on' ? 'https' : 'http');
+
+ /**
+ * Figure out host/port
+ */
+ if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) AND preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) {
+ $host = $matches[1];
+ if (!empty($matches[3])) {
+ $port = $matches[3];
+ } else {
+ $port = $this->getStandardPort($this->protocol);
+ }
+ }
+
+ $this->user = '';
+ $this->pass = '';
+ $this->host = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost');
+ $this->port = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol));
+ $this->path = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/';
+ $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null;
+ $this->anchor = '';
+ }
+
+ // Parse the url and store the various parts
+ if (!empty($url)) {
+ $urlinfo = parse_url($url);
+
+ // Default querystring
+ $this->querystring = array();
+
+ foreach ($urlinfo as $key => $value) {
+ switch ($key) {
+ case 'scheme':
+ $this->protocol = $value;
+ $this->port = $this->getStandardPort($value);
+ break;
+
+ case 'user':
+ case 'pass':
+ case 'host':
+ case 'port':
+ $this->$key = $value;
+ break;
+
+ case 'path':
+ if ($value{0} == '/') {
+ $this->path = $value;
+ } else {
+ $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path);
+ $this->path = sprintf('%s/%s', $path, $value);
+ }
+ break;
+
+ case 'query':
+ $this->querystring = $this->_parseRawQueryString($value);
+ break;
+
+ case 'fragment':
+ $this->anchor = $value;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns full url
+ *
+ * @return string Full url
+ * @access public
+ */
+ function getURL()
+ {
+ $querystring = $this->getQueryString();
+
+ $this->url = $this->protocol . '://'
+ . $this->user . (!empty($this->pass) ? ':' : '')
+ . $this->pass . (!empty($this->user) ? '@' : '')
+ . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port)
+ . $this->path
+ . (!empty($querystring) ? '?' . $querystring : '')
+ . (!empty($this->anchor) ? '#' . $this->anchor : '');
+
+ return $this->url;
+ }
+
+ /**
+ * Adds a querystring item
+ *
+ * @param string $name Name of item
+ * @param string $value Value of item
+ * @param bool $preencoded Whether value is urlencoded or not, default = not
+ * @access public
+ */
+ function addQueryString($name, $value, $preencoded = false)
+ {
+ if ($preencoded) {
+ $this->querystring[$name] = $value;
+ } else {
+ $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value);
+ }
+ }
+
+ /**
+ * Removes a querystring item
+ *
+ * @param string $name Name of item
+ * @access public
+ */
+ function removeQueryString($name)
+ {
+ if (isset($this->querystring[$name])) {
+ unset($this->querystring[$name]);
+ }
+ }
+
+ /**
+ * Sets the querystring to literally what you supply
+ *
+ * @param string $querystring The querystring data. Should be of the format foo=bar&x=y etc
+ * @access public
+ */
+ function addRawQueryString($querystring)
+ {
+ $this->querystring = $this->_parseRawQueryString($querystring);
+ }
+
+ /**
+ * Returns flat querystring
+ *
+ * @return string Querystring
+ * @access public
+ */
+ function getQueryString()
+ {
+ if (!empty($this->querystring)) {
+ foreach ($this->querystring as $name => $value) {
+ if (is_array($value)) {
+ foreach ($value as $k => $v) {
+ $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v);
+ }
+ } elseif (!is_null($value)) {
+ $querystring[] = $name . '=' . $value;
+ } else {
+ $querystring[] = $name;
+ }
+ }
+ $querystring = implode(ini_get('arg_separator.output'), $querystring);
+ } else {
+ $querystring = '';
+ }
+
+ return $querystring;
+ }
+
+ /**
+ * Parses raw querystring and returns an array of it
+ *
+ * @param string $querystring The querystring to parse
+ * @return array An array of the querystring data
+ * @access private
+ */
+ function _parseRawQuerystring($querystring)
+ {
+ $parts = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY);
+ $return = array();
+
+ foreach ($parts as $part) {
+ if (strpos($part, '=') !== false) {
+ $value = substr($part, strpos($part, '=') + 1);
+ $key = substr($part, 0, strpos($part, '='));
+ } else {
+ $value = null;
+ $key = $part;
+ }
+ if (substr($key, -2) == '[]') {
+ $key = substr($key, 0, -2);
+ if (@!is_array($return[$key])) {
+ $return[$key] = array();
+ $return[$key][] = $value;
+ } else {
+ $return[$key][] = $value;
+ }
+ } elseif (!$this->useBrackets AND !empty($return[$key])) {
+ $return[$key] = (array)$return[$key];
+ $return[$key][] = $value;
+ } else {
+ $return[$key] = $value;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Resolves //, ../ and ./ from a path and returns
+ * the result. Eg:
+ *
+ * /foo/bar/../boo.php => /foo/boo.php
+ * /foo/bar/../../boo.php => /boo.php
+ * /foo/bar/.././/boo.php => /foo/boo.php
+ *
+ * This method can also be called statically.
+ *
+ * @param string $url URL path to resolve
+ * @return string The result
+ */
+ function resolvePath($path)
+ {
+ $path = explode('/', str_replace('//', '/', $path));
+
+ for ($i=0; $i<count($path); $i++) {
+ if ($path[$i] == '.') {
+ unset($path[$i]);
+ $path = array_values($path);
+ $i--;
+
+ } elseif ($path[$i] == '..' AND ($i > 1 OR ($i == 1 AND $path[0] != '') ) ) {
+ unset($path[$i]);
+ unset($path[$i-1]);
+ $path = array_values($path);
+ $i -= 2;
+
+ } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') {
+ unset($path[$i]);
+ $path = array_values($path);
+ $i--;
+
+ } else {
+ continue;
+ }
+ }
+
+ return implode('/', $path);
+ }
+
+ /**
+ * Returns the standard port number for a protocol
+ *
+ * @param string $scheme The protocol to lookup
+ * @return integer Port number or NULL if no scheme matches
+ *
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ */
+ function getStandardPort($scheme)
+ {
+ switch (strtolower($scheme)) {
+ case 'http': return 80;
+ case 'https': return 443;
+ case 'ftp': return 21;
+ case 'imap': return 143;
+ case 'imaps': return 993;
+ case 'pop3': return 110;
+ case 'pop3s': return 995;
+ default: return null;
+ }
+ }
+
+ /**
+ * Forces the URL to a particular protocol
+ *
+ * @param string $protocol Protocol to force the URL to
+ * @param integer $port Optional port (standard port is used by default)
+ */
+ function setProtocol($protocol, $port = null)
+ {
+ $this->protocol = $protocol;
+ $this->port = is_null($port) ? $this->getStandardPort() : $port;
+ }
+
+}
+?>
--- /dev/null
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * PEAR class and PEAR_Error class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: PEAR.php 127 2008-01-17 20:21:37Z dcoulter $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**#@+
+ * ERROR constants
+ */
+define('PEAR_ERROR_RETURN', 1);
+define('PEAR_ERROR_PRINT', 2);
+define('PEAR_ERROR_TRIGGER', 4);
+define('PEAR_ERROR_DIE', 8);
+define('PEAR_ERROR_CALLBACK', 16);
+/**
+ * WARNING: obsolete
+ * @deprecated
+ */
+define('PEAR_ERROR_EXCEPTION', 32);
+/**#@-*/
+define('PEAR_ZE2', (function_exists('version_compare') &&
+ version_compare(zend_version(), "2-dev", "ge")));
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ define('OS_WINDOWS', true);
+ define('OS_UNIX', false);
+ define('PEAR_OS', 'Windows');
+} else {
+ define('OS_WINDOWS', false);
+ define('OS_UNIX', true);
+ define('PEAR_OS', 'Unix'); // blatant assumption
+}
+
+// instant backwards compatibility
+if (!defined('PATH_SEPARATOR')) {
+ if (OS_WINDOWS) {
+ define('PATH_SEPARATOR', ';');
+ } else {
+ define('PATH_SEPARATOR', ':');
+ }
+}
+
+$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
+$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
+$GLOBALS['_PEAR_destructor_object_list'] = array();
+$GLOBALS['_PEAR_shutdown_funcs'] = array();
+$GLOBALS['_PEAR_error_handler_stack'] = array();
+
+@ini_set('track_errors', true);
+
+/**
+ * Base class for other PEAR classes. Provides rudimentary
+ * emulation of destructors.
+ *
+ * If you want a destructor in your class, inherit PEAR and make a
+ * destructor method called _yourclassname (same name as the
+ * constructor, but with a "_" prefix). Also, in your constructor you
+ * have to call the PEAR constructor: $this->PEAR();.
+ * The destructor method will be called without parameters. Note that
+ * at in some SAPI implementations (such as Apache), any output during
+ * the request shutdown (in which destructors are called) seems to be
+ * discarded. If you need to get any debug information from your
+ * destructor, use error_log(), syslog() or something similar.
+ *
+ * IMPORTANT! To use the emulated destructors you need to create the
+ * objects by reference: $obj =& new PEAR_child;
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.6.2
+ * @link http://pear.php.net/package/PEAR
+ * @see PEAR_Error
+ * @since Class available since PHP 4.0.2
+ * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
+ */
+class PEAR
+{
+ // {{{ properties
+
+ /**
+ * Whether to enable internal debug messages.
+ *
+ * @var bool
+ * @access private
+ */
+ var $_debug = false;
+
+ /**
+ * Default error mode for this object.
+ *
+ * @var int
+ * @access private
+ */
+ var $_default_error_mode = null;
+
+ /**
+ * Default error options used for this object when error mode
+ * is PEAR_ERROR_TRIGGER.
+ *
+ * @var int
+ * @access private
+ */
+ var $_default_error_options = null;
+
+ /**
+ * Default error handler (callback) for this object, if error mode is
+ * PEAR_ERROR_CALLBACK.
+ *
+ * @var string
+ * @access private
+ */
+ var $_default_error_handler = '';
+
+ /**
+ * Which class to use for error objects.
+ *
+ * @var string
+ * @access private
+ */
+ var $_error_class = 'PEAR_Error';
+
+ /**
+ * An array of expected errors.
+ *
+ * @var array
+ * @access private
+ */
+ var $_expected_errors = array();
+
+ // }}}
+
+ // {{{ constructor
+
+ /**
+ * Constructor. Registers this object in
+ * $_PEAR_destructor_object_list for destructor emulation if a
+ * destructor object exists.
+ *
+ * @param string $error_class (optional) which class to use for
+ * error objects, defaults to PEAR_Error.
+ * @access public
+ * @return void
+ */
+ function PEAR($error_class = null)
+ {
+ $classname = strtolower(get_class($this));
+ if ($this->_debug) {
+ print "PEAR constructor called, class=$classname\n";
+ }
+ if ($error_class !== null) {
+ $this->_error_class = $error_class;
+ }
+ while ($classname && strcasecmp($classname, "pear")) {
+ $destructor = "_$classname";
+ if (method_exists($this, $destructor)) {
+ global $_PEAR_destructor_object_list;
+ $_PEAR_destructor_object_list[] = &$this;
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+
+ // }}}
+ // {{{ destructor
+
+ /**
+ * Destructor (the emulated type of...). Does nothing right now,
+ * but is included for forward compatibility, so subclass
+ * destructors should always call it.
+ *
+ * See the note in the class desciption about output from
+ * destructors.
+ *
+ * @access public
+ * @return void
+ */
+ function _PEAR() {
+ if ($this->_debug) {
+ printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
+ }
+ }
+
+ // }}}
+ // {{{ getStaticProperty()
+
+ /**
+ * If you have a class that's mostly/entirely static, and you need static
+ * properties, you can use this method to simulate them. Eg. in your method(s)
+ * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+ * You MUST use a reference, or they will not persist!
+ *
+ * @access public
+ * @param string $class The calling classname, to prevent clashes
+ * @param string $var The variable to retrieve.
+ * @return mixed A reference to the variable. If not set it will be
+ * auto initialised to NULL.
+ */
+ function &getStaticProperty($class, $var)
+ {
+ static $properties;
+ if (!isset($properties[$class])) {
+ $properties[$class] = array();
+ }
+ if (!array_key_exists($var, $properties[$class])) {
+ $properties[$class][$var] = null;
+ }
+ return $properties[$class][$var];
+ }
+
+ // }}}
+ // {{{ registerShutdownFunc()
+
+ /**
+ * Use this function to register a shutdown method for static
+ * classes.
+ *
+ * @access public
+ * @param mixed $func The function name (or array of class/method) to call
+ * @param mixed $args The arguments to pass to the function
+ * @return void
+ */
+ function registerShutdownFunc($func, $args = array())
+ {
+ // if we are called statically, there is a potential
+ // that no shutdown func is registered. Bug #6445
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
+ }
+
+ // }}}
+ // {{{ isError()
+
+ /**
+ * Tell whether a value is a PEAR error.
+ *
+ * @param mixed $data the value to test
+ * @param int $code if $data is an error object, return true
+ * only if $code is a string and
+ * $obj->getMessage() == $code or
+ * $code is an integer and $obj->getCode() == $code
+ * @access public
+ * @return bool true if parameter is an error
+ */
+ function isError($data, $code = null)
+ {
+ if (is_a($data, 'PEAR_Error')) {
+ if (is_null($code)) {
+ return true;
+ } elseif (is_string($code)) {
+ return $data->getMessage() == $code;
+ } else {
+ return $data->getCode() == $code;
+ }
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ setErrorHandling()
+
+ /**
+ * Sets how errors generated by this object should be handled.
+ * Can be invoked both in objects and statically. If called
+ * statically, setErrorHandling sets the default behaviour for all
+ * PEAR objects. If called in an object, setErrorHandling sets
+ * the default behaviour for that object.
+ *
+ * @param int $mode
+ * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options
+ * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+ * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ *
+ * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+ * to be the callback function or method. A callback
+ * function is a string with the name of the function, a
+ * callback method is an array of two elements: the element
+ * at index 0 is the object, and the element at index 1 is
+ * the name of the method to call in the object.
+ *
+ * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+ * a printf format string used when printing the error
+ * message.
+ *
+ * @access public
+ * @return void
+ * @see PEAR_ERROR_RETURN
+ * @see PEAR_ERROR_PRINT
+ * @see PEAR_ERROR_TRIGGER
+ * @see PEAR_ERROR_DIE
+ * @see PEAR_ERROR_CALLBACK
+ * @see PEAR_ERROR_EXCEPTION
+ *
+ * @since PHP 4.0.5
+ */
+
+ function setErrorHandling($mode = null, $options = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $setmode = &$this->_default_error_mode;
+ $setoptions = &$this->_default_error_options;
+ } else {
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ }
+
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ }
+
+ // }}}
+ // {{{ expectError()
+
+ /**
+ * This method is used to tell which errors you expect to get.
+ * Expected errors are always returned with error mode
+ * PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
+ * and this method pushes a new element onto it. The list of
+ * expected errors are in effect until they are popped off the
+ * stack with the popExpect() method.
+ *
+ * Note that this method can not be called statically
+ *
+ * @param mixed $code a single error code or an array of error codes to expect
+ *
+ * @return int the new depth of the "expected errors" stack
+ * @access public
+ */
+ function expectError($code = '*')
+ {
+ if (is_array($code)) {
+ array_push($this->_expected_errors, $code);
+ } else {
+ array_push($this->_expected_errors, array($code));
+ }
+ return sizeof($this->_expected_errors);
+ }
+
+ // }}}
+ // {{{ popExpect()
+
+ /**
+ * This method pops one element off the expected error codes
+ * stack.
+ *
+ * @return array the list of error codes that were popped
+ */
+ function popExpect()
+ {
+ return array_pop($this->_expected_errors);
+ }
+
+ // }}}
+ // {{{ _checkDelExpect()
+
+ /**
+ * This method checks unsets an error code if available
+ *
+ * @param mixed error code
+ * @return bool true if the error code was unset, false otherwise
+ * @access private
+ * @since PHP 4.3.0
+ */
+ function _checkDelExpect($error_code)
+ {
+ $deleted = false;
+
+ foreach ($this->_expected_errors AS $key => $error_array) {
+ if (in_array($error_code, $error_array)) {
+ unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
+ $deleted = true;
+ }
+
+ // clean up empty arrays
+ if (0 == count($this->_expected_errors[$key])) {
+ unset($this->_expected_errors[$key]);
+ }
+ }
+ return $deleted;
+ }
+
+ // }}}
+ // {{{ delExpect()
+
+ /**
+ * This method deletes all occurences of the specified element from
+ * the expected error codes stack.
+ *
+ * @param mixed $error_code error code that should be deleted
+ * @return mixed list of error codes that were deleted or error
+ * @access public
+ * @since PHP 4.3.0
+ */
+ function delExpect($error_code)
+ {
+ $deleted = false;
+
+ if ((is_array($error_code) && (0 != count($error_code)))) {
+ // $error_code is a non-empty array here;
+ // we walk through it trying to unset all
+ // values
+ foreach($error_code as $key => $error) {
+ if ($this->_checkDelExpect($error)) {
+ $deleted = true;
+ } else {
+ $deleted = false;
+ }
+ }
+ return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ } elseif (!empty($error_code)) {
+ // $error_code comes alone, trying to unset it
+ if ($this->_checkDelExpect($error_code)) {
+ return true;
+ } else {
+ return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ }
+ } else {
+ // $error_code is empty
+ return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
+ }
+ }
+
+ // }}}
+ // {{{ raiseError()
+
+ /**
+ * This method is a wrapper that returns an instance of the
+ * configured error class with this object's default error
+ * handling applied. If the $mode and $options parameters are not
+ * specified, the object's defaults are used.
+ *
+ * @param mixed $message a text error message or a PEAR error object
+ *
+ * @param int $code a numeric error code (it is up to your class
+ * to define these if you want to use codes)
+ *
+ * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
+ * specifies the PHP-internal error level (one of
+ * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ * If $mode is PEAR_ERROR_CALLBACK, this
+ * parameter specifies the callback function or
+ * method. In other error modes this parameter
+ * is ignored.
+ *
+ * @param string $userinfo If you need to pass along for example debug
+ * information, this parameter is meant for that.
+ *
+ * @param string $error_class The returned error object will be
+ * instantiated from this class, if specified.
+ *
+ * @param bool $skipmsg If true, raiseError will only pass error codes,
+ * the error message parameter will be dropped.
+ *
+ * @access public
+ * @return object a PEAR error object
+ * @see PEAR::setErrorHandling
+ * @since PHP 4.0.5
+ */
+ function &raiseError($message = null,
+ $code = null,
+ $mode = null,
+ $options = null,
+ $userinfo = null,
+ $error_class = null,
+ $skipmsg = false)
+ {
+ // The error is yet a PEAR error object
+ if (is_object($message)) {
+ $code = $message->getCode();
+ $userinfo = $message->getUserInfo();
+ $error_class = $message->getType();
+ $message->error_message_prefix = '';
+ $message = $message->getMessage();
+ }
+
+ if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) {
+ if ($exp[0] == "*" ||
+ (is_int(reset($exp)) && in_array($code, $exp)) ||
+ (is_string(reset($exp)) && in_array($message, $exp))) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ }
+ // No mode given, try global ones
+ if ($mode === null) {
+ // Class error handler
+ if (isset($this) && isset($this->_default_error_mode)) {
+ $mode = $this->_default_error_mode;
+ $options = $this->_default_error_options;
+ // Global error handler
+ } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
+ $mode = $GLOBALS['_PEAR_default_error_mode'];
+ $options = $GLOBALS['_PEAR_default_error_options'];
+ }
+ }
+
+ if ($error_class !== null) {
+ $ec = $error_class;
+ } elseif (isset($this) && isset($this->_error_class)) {
+ $ec = $this->_error_class;
+ } else {
+ $ec = 'PEAR_Error';
+ }
+ if ($skipmsg) {
+ $a = &new $ec($code, $mode, $options, $userinfo);
+ return $a;
+ } else {
+ $a = &new $ec($message, $code, $mode, $options, $userinfo);
+ return $a;
+ }
+ }
+
+ // }}}
+ // {{{ throwError()
+
+ /**
+ * Simpler form of raiseError with fewer options. In most cases
+ * message, code and userinfo are enough.
+ *
+ * @param string $message
+ *
+ */
+ function &throwError($message = null,
+ $code = null,
+ $userinfo = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $a = &$this->raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ } else {
+ $a = &PEAR::raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ }
+ }
+
+ // }}}
+ function staticPushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ $stack[] = array($def_mode, $def_options);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $def_mode = $mode;
+ $def_options = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $def_mode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $def_options = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ function staticPopErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ return true;
+ }
+
+ // {{{ pushErrorHandling()
+
+ /**
+ * Push a new error handler on top of the error handler options stack. With this
+ * you can easily override the actual error handler for some code and restore
+ * it later with popErrorHandling.
+ *
+ * @param mixed $mode (same as setErrorHandling)
+ * @param mixed $options (same as setErrorHandling)
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::setErrorHandling
+ */
+ function pushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $def_mode = &$this->_default_error_mode;
+ $def_options = &$this->_default_error_options;
+ } else {
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ }
+ $stack[] = array($def_mode, $def_options);
+
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ // }}}
+ // {{{ popErrorHandling()
+
+ /**
+ * Pop the last error handler used
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::pushErrorHandling
+ */
+ function popErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ loadExtension()
+
+ /**
+ * OS independant PHP extension load. Remember to take care
+ * on the correct extension name for case sensitive OSes.
+ *
+ * @param string $ext The extension name
+ * @return bool Success or not on the dl() call
+ */
+ function loadExtension($ext)
+ {
+ if (!extension_loaded($ext)) {
+ // if either returns true dl() will produce a FATAL error, stop that
+ if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
+ return false;
+ }
+ if (OS_WINDOWS) {
+ $suffix = '.dll';
+ } elseif (PHP_OS == 'HP-UX') {
+ $suffix = '.sl';
+ } elseif (PHP_OS == 'AIX') {
+ $suffix = '.a';
+ } elseif (PHP_OS == 'OSX') {
+ $suffix = '.bundle';
+ } else {
+ $suffix = '.so';
+ }
+ return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+ }
+ return true;
+ }
+
+ // }}}
+}
+
+// {{{ _PEAR_call_destructors()
+
+function _PEAR_call_destructors()
+{
+ global $_PEAR_destructor_object_list;
+ if (is_array($_PEAR_destructor_object_list) &&
+ sizeof($_PEAR_destructor_object_list))
+ {
+ reset($_PEAR_destructor_object_list);
+ if (PEAR::getStaticProperty('PEAR', 'destructlifo')) {
+ $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
+ }
+ while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
+ $classname = get_class($objref);
+ while ($classname) {
+ $destructor = "_$classname";
+ if (method_exists($objref, $destructor)) {
+ $objref->$destructor();
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+ // Empty the object list to ensure that destructors are
+ // not called more than once.
+ $_PEAR_destructor_object_list = array();
+ }
+
+ // Now call the shutdown functions
+ if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) {
+ foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
+ call_user_func_array($value[0], $value[1]);
+ }
+ }
+}
+
+// }}}
+/**
+ * Standard PEAR error class for PHP 4
+ *
+ * This class is supserseded by {@link PEAR_Exception} in PHP 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Gregory Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.6.2
+ * @link http://pear.php.net/manual/en/core.pear.pear-error.php
+ * @see PEAR::raiseError(), PEAR::throwError()
+ * @since Class available since PHP 4.0.2
+ */
+class PEAR_Error
+{
+ // {{{ properties
+
+ var $error_message_prefix = '';
+ var $mode = PEAR_ERROR_RETURN;
+ var $level = E_USER_NOTICE;
+ var $code = -1;
+ var $message = '';
+ var $userinfo = '';
+ var $backtrace = null;
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * PEAR_Error constructor
+ *
+ * @param string $message message
+ *
+ * @param int $code (optional) error code
+ *
+ * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
+ * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
+ *
+ * @param mixed $options (optional) error level, _OR_ in the case of
+ * PEAR_ERROR_CALLBACK, the callback function or object/method
+ * tuple.
+ *
+ * @param string $userinfo (optional) additional user/debug info
+ *
+ * @access public
+ *
+ */
+ function PEAR_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ if ($mode === null) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ $this->message = $message;
+ $this->code = $code;
+ $this->mode = $mode;
+ $this->userinfo = $userinfo;
+ if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) {
+ $this->backtrace = debug_backtrace();
+ if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
+ unset($this->backtrace[0]['object']);
+ }
+ }
+ if ($mode & PEAR_ERROR_CALLBACK) {
+ $this->level = E_USER_NOTICE;
+ $this->callback = $options;
+ } else {
+ if ($options === null) {
+ $options = E_USER_NOTICE;
+ }
+ $this->level = $options;
+ $this->callback = null;
+ }
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ } else {
+ $format = $options;
+ }
+ printf($format, $this->getMessage());
+ }
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ trigger_error($this->getMessage(), $this->level);
+ }
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $msg = $this->getMessage();
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ if (substr($msg, -1) != "\n") {
+ $msg .= "\n";
+ }
+ } else {
+ $format = $options;
+ }
+ die(sprintf($format, $msg));
+ }
+ if ($this->mode & PEAR_ERROR_CALLBACK) {
+ if (is_callable($this->callback)) {
+ call_user_func($this->callback, $this);
+ }
+ }
+ if ($this->mode & PEAR_ERROR_EXCEPTION) {
+ trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
+ eval('$e = new Exception($this->message, $this->code);throw($e);');
+ }
+ }
+
+ // }}}
+ // {{{ getMode()
+
+ /**
+ * Get the error mode from an error object.
+ *
+ * @return int error mode
+ * @access public
+ */
+ function getMode() {
+ return $this->mode;
+ }
+
+ // }}}
+ // {{{ getCallback()
+
+ /**
+ * Get the callback function/method from an error object.
+ *
+ * @return mixed callback function or object/method array
+ * @access public
+ */
+ function getCallback() {
+ return $this->callback;
+ }
+
+ // }}}
+ // {{{ getMessage()
+
+
+ /**
+ * Get the error message from an error object.
+ *
+ * @return string full error message
+ * @access public
+ */
+ function getMessage()
+ {
+ return ($this->error_message_prefix . $this->message);
+ }
+
+
+ // }}}
+ // {{{ getCode()
+
+ /**
+ * Get error code from an error object
+ *
+ * @return int error code
+ * @access public
+ */
+ function getCode()
+ {
+ return $this->code;
+ }
+
+ // }}}
+ // {{{ getType()
+
+ /**
+ * Get the name of this error/exception.
+ *
+ * @return string error/exception name (type)
+ * @access public
+ */
+ function getType()
+ {
+ return get_class($this);
+ }
+
+ // }}}
+ // {{{ getUserInfo()
+
+ /**
+ * Get additional user-supplied information.
+ *
+ * @return string user-supplied information
+ * @access public
+ */
+ function getUserInfo()
+ {
+ return $this->userinfo;
+ }
+
+ // }}}
+ // {{{ getDebugInfo()
+
+ /**
+ * Get additional debug information supplied by the application.
+ *
+ * @return string debug information
+ * @access public
+ */
+ function getDebugInfo()
+ {
+ return $this->getUserInfo();
+ }
+
+ // }}}
+ // {{{ getBacktrace()
+
+ /**
+ * Get the call backtrace from where the error was generated.
+ * Supported with PHP 4.3.0 or newer.
+ *
+ * @param int $frame (optional) what frame to fetch
+ * @return array Backtrace, or NULL if not available.
+ * @access public
+ */
+ function getBacktrace($frame = null)
+ {
+ if (defined('PEAR_IGNORE_BACKTRACE')) {
+ return null;
+ }
+ if ($frame === null) {
+ return $this->backtrace;
+ }
+ return $this->backtrace[$frame];
+ }
+
+ // }}}
+ // {{{ addUserInfo()
+
+ function addUserInfo($info)
+ {
+ if (empty($this->userinfo)) {
+ $this->userinfo = $info;
+ } else {
+ $this->userinfo .= " ** $info";
+ }
+ }
+
+ // }}}
+ // {{{ toString()
+
+ /**
+ * Make a string representation of this object.
+ *
+ * @return string a string with an object summary
+ * @access public
+ */
+ function toString() {
+ $modes = array();
+ $levels = array(E_USER_NOTICE => 'notice',
+ E_USER_WARNING => 'warning',
+ E_USER_ERROR => 'error');
+ if ($this->mode & PEAR_ERROR_CALLBACK) {
+ if (is_array($this->callback)) {
+ $callback = (is_object($this->callback[0]) ?
+ strtolower(get_class($this->callback[0])) :
+ $this->callback[0]) . '::' .
+ $this->callback[1];
+ } else {
+ $callback = $this->callback;
+ }
+ return sprintf('[%s: message="%s" code=%d mode=callback '.
+ 'callback=%s prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ $callback, $this->error_message_prefix,
+ $this->userinfo);
+ }
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ $modes[] = 'print';
+ }
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ $modes[] = 'trigger';
+ }
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $modes[] = 'die';
+ }
+ if ($this->mode & PEAR_ERROR_RETURN) {
+ $modes[] = 'return';
+ }
+ return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
+ 'prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ implode("|", $modes), $levels[$this->level],
+ $this->error_message_prefix,
+ $this->userinfo);
+ }
+
+ // }}}
+}
+
+/*
+ * Local Variables:
+ * mode: php
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+?>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+/**
+ * PEAR_Exception
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Hans Lellelid <hans@velum.net>
+ * @author Bertrand Mansion <bmansion@mamasam.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Exception.php,v 1.28 2007/05/07 01:58:54 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.3.3
+ */
+
+
+/**
+ * Base PEAR_Exception Class
+ *
+ * 1) Features:
+ *
+ * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
+ * - Definable triggers, shot when exceptions occur
+ * - Pretty and informative error messages
+ * - Added more context info available (like class, method or cause)
+ * - cause can be a PEAR_Exception or an array of mixed
+ * PEAR_Exceptions/PEAR_ErrorStack warnings
+ * - callbacks for specific exception classes and their children
+ *
+ * 2) Ideas:
+ *
+ * - Maybe a way to define a 'template' for the output
+ *
+ * 3) Inherited properties from PHP Exception Class:
+ *
+ * protected $message
+ * protected $code
+ * protected $line
+ * protected $file
+ * private $trace
+ *
+ * 4) Inherited methods from PHP Exception Class:
+ *
+ * __clone
+ * __construct
+ * getMessage
+ * getCode
+ * getFile
+ * getLine
+ * getTraceSafe
+ * getTraceSafeAsString
+ * __toString
+ *
+ * 5) Usage example
+ *
+ * <code>
+ * require_once 'PEAR/Exception.php';
+ *
+ * class Test {
+ * function foo() {
+ * throw new PEAR_Exception('Error Message', ERROR_CODE);
+ * }
+ * }
+ *
+ * function myLogger($pear_exception) {
+ * echo $pear_exception->getMessage();
+ * }
+ * // each time a exception is thrown the 'myLogger' will be called
+ * // (its use is completely optional)
+ * PEAR_Exception::addObserver('myLogger');
+ * $test = new Test;
+ * try {
+ * $test->foo();
+ * } catch (PEAR_Exception $e) {
+ * print $e;
+ * }
+ * </code>
+ *
+ * @category pear
+ * @package PEAR
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Hans Lellelid <hans@velum.net>
+ * @author Bertrand Mansion <bmansion@mamasam.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.6.2
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.3.3
+ *
+ */
+class PEAR_Exception extends Exception
+{
+ const OBSERVER_PRINT = -2;
+ const OBSERVER_TRIGGER = -4;
+ const OBSERVER_DIE = -8;
+ protected $cause;
+ private static $_observers = array();
+ private static $_uniqueid = 0;
+ private $_trace;
+
+ /**
+ * Supported signatures:
+ * - PEAR_Exception(string $message);
+ * - PEAR_Exception(string $message, int $code);
+ * - PEAR_Exception(string $message, Exception $cause);
+ * - PEAR_Exception(string $message, Exception $cause, int $code);
+ * - PEAR_Exception(string $message, PEAR_Error $cause);
+ * - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
+ * - PEAR_Exception(string $message, array $causes);
+ * - PEAR_Exception(string $message, array $causes, int $code);
+ * @param string exception message
+ * @param int|Exception|PEAR_Error|array|null exception cause
+ * @param int|null exception code or null
+ */
+ public function __construct($message, $p2 = null, $p3 = null)
+ {
+ if (is_int($p2)) {
+ $code = $p2;
+ $this->cause = null;
+ } elseif (is_object($p2) || is_array($p2)) {
+ // using is_object allows both Exception and PEAR_Error
+ if (is_object($p2) && !($p2 instanceof Exception)) {
+ if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
+ throw new PEAR_Exception('exception cause must be Exception, ' .
+ 'array, or PEAR_Error');
+ }
+ }
+ $code = $p3;
+ if (is_array($p2) && isset($p2['message'])) {
+ // fix potential problem of passing in a single warning
+ $p2 = array($p2);
+ }
+ $this->cause = $p2;
+ } else {
+ $code = null;
+ $this->cause = null;
+ }
+ parent::__construct($message, $code);
+ $this->signal();
+ }
+
+ /**
+ * @param mixed $callback - A valid php callback, see php func is_callable()
+ * - A PEAR_Exception::OBSERVER_* constant
+ * - An array(const PEAR_Exception::OBSERVER_*,
+ * mixed $options)
+ * @param string $label The name of the observer. Use this if you want
+ * to remove it later with removeObserver()
+ */
+ public static function addObserver($callback, $label = 'default')
+ {
+ self::$_observers[$label] = $callback;
+ }
+
+ public static function removeObserver($label = 'default')
+ {
+ unset(self::$_observers[$label]);
+ }
+
+ /**
+ * @return int unique identifier for an observer
+ */
+ public static function getUniqueId()
+ {
+ return self::$_uniqueid++;
+ }
+
+ private function signal()
+ {
+ foreach (self::$_observers as $func) {
+ if (is_callable($func)) {
+ call_user_func($func, $this);
+ continue;
+ }
+ settype($func, 'array');
+ switch ($func[0]) {
+ case self::OBSERVER_PRINT :
+ $f = (isset($func[1])) ? $func[1] : '%s';
+ printf($f, $this->getMessage());
+ break;
+ case self::OBSERVER_TRIGGER :
+ $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
+ trigger_error($this->getMessage(), $f);
+ break;
+ case self::OBSERVER_DIE :
+ $f = (isset($func[1])) ? $func[1] : '%s';
+ die(printf($f, $this->getMessage()));
+ break;
+ default:
+ trigger_error('invalid observer type', E_USER_WARNING);
+ }
+ }
+ }
+
+ /**
+ * Return specific error information that can be used for more detailed
+ * error messages or translation.
+ *
+ * This method may be overridden in child exception classes in order
+ * to add functionality not present in PEAR_Exception and is a placeholder
+ * to define API
+ *
+ * The returned array must be an associative array of parameter => value like so:
+ * <pre>
+ * array('name' => $name, 'context' => array(...))
+ * </pre>
+ * @return array
+ */
+ public function getErrorData()
+ {
+ return array();
+ }
+
+ /**
+ * Returns the exception that caused this exception to be thrown
+ * @access public
+ * @return Exception|array The context of the exception
+ */
+ public function getCause()
+ {
+ return $this->cause;
+ }
+
+ /**
+ * Function must be public to call on caused exceptions
+ * @param array
+ */
+ public function getCauseMessage(&$causes)
+ {
+ $trace = $this->getTraceSafe();
+ $cause = array('class' => get_class($this),
+ 'message' => $this->message,
+ 'file' => 'unknown',
+ 'line' => 'unknown');
+ if (isset($trace[0])) {
+ if (isset($trace[0]['file'])) {
+ $cause['file'] = $trace[0]['file'];
+ $cause['line'] = $trace[0]['line'];
+ }
+ }
+ $causes[] = $cause;
+ if ($this->cause instanceof PEAR_Exception) {
+ $this->cause->getCauseMessage($causes);
+ } elseif ($this->cause instanceof Exception) {
+ $causes[] = array('class' => get_class($this->cause),
+ 'message' => $this->cause->getMessage(),
+ 'file' => $this->cause->getFile(),
+ 'line' => $this->cause->getLine());
+ } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
+ $causes[] = array('class' => get_class($this->cause),
+ 'message' => $this->cause->getMessage(),
+ 'file' => 'unknown',
+ 'line' => 'unknown');
+ } elseif (is_array($this->cause)) {
+ foreach ($this->cause as $cause) {
+ if ($cause instanceof PEAR_Exception) {
+ $cause->getCauseMessage($causes);
+ } elseif ($cause instanceof Exception) {
+ $causes[] = array('class' => get_class($cause),
+ 'message' => $cause->getMessage(),
+ 'file' => $cause->getFile(),
+ 'line' => $cause->getLine());
+ } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) {
+ $causes[] = array('class' => get_class($cause),
+ 'message' => $cause->getMessage(),
+ 'file' => 'unknown',
+ 'line' => 'unknown');
+ } elseif (is_array($cause) && isset($cause['message'])) {
+ // PEAR_ErrorStack warning
+ $causes[] = array(
+ 'class' => $cause['package'],
+ 'message' => $cause['message'],
+ 'file' => isset($cause['context']['file']) ?
+ $cause['context']['file'] :
+ 'unknown',
+ 'line' => isset($cause['context']['line']) ?
+ $cause['context']['line'] :
+ 'unknown',
+ );
+ }
+ }
+ }
+ }
+
+ public function getTraceSafe()
+ {
+ if (!isset($this->_trace)) {
+ $this->_trace = $this->getTrace();
+ if (empty($this->_trace)) {
+ $backtrace = debug_backtrace();
+ $this->_trace = array($backtrace[count($backtrace)-1]);
+ }
+ }
+ return $this->_trace;
+ }
+
+ public function getErrorClass()
+ {
+ $trace = $this->getTraceSafe();
+ return $trace[0]['class'];
+ }
+
+ public function getErrorMethod()
+ {
+ $trace = $this->getTraceSafe();
+ return $trace[0]['function'];
+ }
+
+ public function __toString()
+ {
+ if (isset($_SERVER['REQUEST_URI'])) {
+ return $this->toHtml();
+ }
+ return $this->toText();
+ }
+
+ public function toHtml()
+ {
+ $trace = $this->getTraceSafe();
+ $causes = array();
+ $this->getCauseMessage($causes);
+ $html = '<table border="1" cellspacing="0">' . "\n";
+ foreach ($causes as $i => $cause) {
+ $html .= '<tr><td colspan="3" bgcolor="#ff9999">'
+ . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
+ . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
+ . 'on line <b>' . $cause['line'] . '</b>'
+ . "</td></tr>\n";
+ }
+ $html .= '<tr><td colspan="3" bgcolor="#aaaaaa" align="center"><b>Exception trace</b></td></tr>' . "\n"
+ . '<tr><td align="center" bgcolor="#cccccc" width="20"><b>#</b></td>'
+ . '<td align="center" bgcolor="#cccccc"><b>Function</b></td>'
+ . '<td align="center" bgcolor="#cccccc"><b>Location</b></td></tr>' . "\n";
+
+ foreach ($trace as $k => $v) {
+ $html .= '<tr><td align="center">' . $k . '</td>'
+ . '<td>';
+ if (!empty($v['class'])) {
+ $html .= $v['class'] . $v['type'];
+ }
+ $html .= $v['function'];
+ $args = array();
+ if (!empty($v['args'])) {
+ foreach ($v['args'] as $arg) {
+ if (is_null($arg)) $args[] = 'null';
+ elseif (is_array($arg)) $args[] = 'Array';
+ elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
+ elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
+ elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
+ else {
+ $arg = (string)$arg;
+ $str = htmlspecialchars(substr($arg, 0, 16));
+ if (strlen($arg) > 16) $str .= '…';
+ $args[] = "'" . $str . "'";
+ }
+ }
+ }
+ $html .= '(' . implode(', ',$args) . ')'
+ . '</td>'
+ . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
+ . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
+ . '</td></tr>' . "\n";
+ }
+ $html .= '<tr><td align="center">' . ($k+1) . '</td>'
+ . '<td>{main}</td>'
+ . '<td> </td></tr>' . "\n"
+ . '</table>';
+ return $html;
+ }
+
+ public function toText()
+ {
+ $causes = array();
+ $this->getCauseMessage($causes);
+ $causeMsg = '';
+ foreach ($causes as $i => $cause) {
+ $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
+ . $cause['message'] . ' in ' . $cause['file']
+ . ' on line ' . $cause['line'] . "\n";
+ }
+ return $causeMsg . $this->getTraceAsString();
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+
+/*************************************************
+
+Snoopy - the PHP net client
+Author: Monte Ohrt <monte@ispi.net>
+Copyright (c): 1999-2000 ispi, all rights reserved
+Version: 1.01
+
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+You may contact the author of Snoopy by e-mail at:
+monte@ispi.net
+
+Or, write to:
+Monte Ohrt
+CTO, ispi
+237 S. 70th suite 220
+Lincoln, NE 68510
+
+The latest version of Snoopy can be obtained from:
+http://snoopy.sourceforge.net/
+
+*************************************************/
+
+class Snoopy
+{
+ /**** Public variables ****/
+
+ /* user definable vars */
+
+ var $host = "www.php.net"; // host name we are connecting to
+ var $port = 80; // port we are connecting to
+ var $proxy_host = ""; // proxy host to use
+ var $proxy_port = ""; // proxy port to use
+ var $proxy_user = ""; // proxy user to use
+ var $proxy_pass = ""; // proxy password to use
+
+ var $agent = "Snoopy v1.2.3"; // agent we masquerade as
+ var $referer = ""; // referer info to pass
+ var $cookies = array(); // array of cookies to pass
+ // $cookies["username"]="joe";
+ var $rawheaders = array(); // array of raw headers to send
+ // $rawheaders["Content-type"]="text/html";
+
+ var $maxredirs = 5; // http redirection depth maximum. 0 = disallow
+ var $lastredirectaddr = ""; // contains address of last redirected address
+ var $offsiteok = true; // allows redirection off-site
+ var $maxframes = 0; // frame content depth maximum. 0 = disallow
+ var $expandlinks = true; // expand links to fully qualified URLs.
+ // this only applies to fetchlinks()
+ // submitlinks(), and submittext()
+ var $passcookies = true; // pass set cookies back through redirects
+ // NOTE: this currently does not respect
+ // dates, domains or paths.
+
+ var $user = ""; // user for http authentication
+ var $pass = ""; // password for http authentication
+
+ // http accept types
+ var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*";
+
+ var $results = ""; // where the content is put
+
+ var $error = ""; // error messages sent here
+ var $response_code = ""; // response code returned from server
+ var $headers = array(); // headers returned from server sent here
+ var $maxlength = 500000; // max return data length (body)
+ var $read_timeout = 0; // timeout on read operations, in seconds
+ // supported only since PHP 4 Beta 4
+ // set to 0 to disallow timeouts
+ var $timed_out = false; // if a read operation timed out
+ var $status = 0; // http request status
+
+ var $temp_dir = "/tmp"; // temporary directory that the webserver
+ // has permission to write to.
+ // under Windows, this should be C:\temp
+
+ var $curl_path = "/usr/local/bin/curl";
+ // Snoopy will use cURL for fetching
+ // SSL content if a full system path to
+ // the cURL binary is supplied here.
+ // set to false if you do not have
+ // cURL installed. See http://curl.haxx.se
+ // for details on installing cURL.
+ // Snoopy does *not* use the cURL
+ // library functions built into php,
+ // as these functions are not stable
+ // as of this Snoopy release.
+
+ /**** Private variables ****/
+
+ var $_maxlinelen = 4096; // max line length (headers)
+
+ var $_httpmethod = "GET"; // default http request method
+ var $_httpversion = "HTTP/1.0"; // default http request version
+ var $_submit_method = "POST"; // default submit method
+ var $_submit_type = "application/x-www-form-urlencoded"; // default submit type
+ var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type
+ var $_redirectaddr = false; // will be set if page fetched is a redirect
+ var $_redirectdepth = 0; // increments on an http redirect
+ var $_frameurls = array(); // frame src urls
+ var $_framedepth = 0; // increments on frame depth
+
+ var $_isproxy = false; // set if using a proxy server
+ var $_fp_timeout = 30; // timeout for socket connection
+
+/*======================================================================*\
+ Function: fetch
+ Purpose: fetch the contents of a web page
+ (and possibly other protocols in the
+ future like ftp, nntp, gopher, etc.)
+ Input: $URI the location of the page to fetch
+ Output: $this->results the output text from the fetch
+\*======================================================================*/
+
+ function fetch($URI)
+ {
+
+ //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS);
+ $URI_PARTS = parse_url($URI);
+ if (!empty($URI_PARTS["user"]))
+ $this->user = $URI_PARTS["user"];
+ if (!empty($URI_PARTS["pass"]))
+ $this->pass = $URI_PARTS["pass"];
+ if (empty($URI_PARTS["query"]))
+ $URI_PARTS["query"] = '';
+ if (empty($URI_PARTS["path"]))
+ $URI_PARTS["path"] = '';
+
+ switch(strtolower($URI_PARTS["scheme"]))
+ {
+ case "http":
+ $this->host = $URI_PARTS["host"];
+ if(!empty($URI_PARTS["port"]))
+ $this->port = $URI_PARTS["port"];
+ if($this->_connect($fp))
+ {
+ if($this->_isproxy)
+ {
+ // using proxy, send entire URI
+ $this->_httprequest($URI,$fp,$URI,$this->_httpmethod);
+ }
+ else
+ {
+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
+ // no proxy, send only the path
+ $this->_httprequest($path, $fp, $URI, $this->_httpmethod);
+ }
+
+ $this->_disconnect($fp);
+
+ if($this->_redirectaddr)
+ {
+ /* url was redirected, check if we've hit the max depth */
+ if($this->maxredirs > $this->_redirectdepth)
+ {
+ // only follow redirect if it's on this site, or offsiteok is true
+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
+ {
+ /* follow the redirect */
+ $this->_redirectdepth++;
+ $this->lastredirectaddr=$this->_redirectaddr;
+ $this->fetch($this->_redirectaddr);
+ }
+ }
+ }
+
+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
+ {
+ $frameurls = $this->_frameurls;
+ $this->_frameurls = array();
+
+ while(list(,$frameurl) = each($frameurls))
+ {
+ if($this->_framedepth < $this->maxframes)
+ {
+ $this->fetch($frameurl);
+ $this->_framedepth++;
+ }
+ else
+ break;
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ break;
+ case "https":
+ if(!$this->curl_path)
+ return false;
+ if(function_exists("is_executable"))
+ if (!is_executable($this->curl_path))
+ return false;
+ $this->host = $URI_PARTS["host"];
+ if(!empty($URI_PARTS["port"]))
+ $this->port = $URI_PARTS["port"];
+ if($this->_isproxy)
+ {
+ // using proxy, send entire URI
+ $this->_httpsrequest($URI,$URI,$this->_httpmethod);
+ }
+ else
+ {
+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
+ // no proxy, send only the path
+ $this->_httpsrequest($path, $URI, $this->_httpmethod);
+ }
+
+ if($this->_redirectaddr)
+ {
+ /* url was redirected, check if we've hit the max depth */
+ if($this->maxredirs > $this->_redirectdepth)
+ {
+ // only follow redirect if it's on this site, or offsiteok is true
+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
+ {
+ /* follow the redirect */
+ $this->_redirectdepth++;
+ $this->lastredirectaddr=$this->_redirectaddr;
+ $this->fetch($this->_redirectaddr);
+ }
+ }
+ }
+
+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
+ {
+ $frameurls = $this->_frameurls;
+ $this->_frameurls = array();
+
+ while(list(,$frameurl) = each($frameurls))
+ {
+ if($this->_framedepth < $this->maxframes)
+ {
+ $this->fetch($frameurl);
+ $this->_framedepth++;
+ }
+ else
+ break;
+ }
+ }
+ return true;
+ break;
+ default:
+ // not a valid protocol
+ $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n';
+ return false;
+ break;
+ }
+ return true;
+ }
+
+/*======================================================================*\
+ Function: submit
+ Purpose: submit an http form
+ Input: $URI the location to post the data
+ $formvars the formvars to use.
+ format: $formvars["var"] = "val";
+ $formfiles an array of files to submit
+ format: $formfiles["var"] = "/dir/filename.ext";
+ Output: $this->results the text output from the post
+\*======================================================================*/
+
+ function submit($URI, $formvars="", $formfiles="")
+ {
+ unset($postdata);
+
+ $postdata = $this->_prepare_post_body($formvars, $formfiles);
+
+ $URI_PARTS = parse_url($URI);
+ if (!empty($URI_PARTS["user"]))
+ $this->user = $URI_PARTS["user"];
+ if (!empty($URI_PARTS["pass"]))
+ $this->pass = $URI_PARTS["pass"];
+ if (empty($URI_PARTS["query"]))
+ $URI_PARTS["query"] = '';
+ if (empty($URI_PARTS["path"]))
+ $URI_PARTS["path"] = '';
+
+ switch(strtolower($URI_PARTS["scheme"]))
+ {
+ case "http":
+ $this->host = $URI_PARTS["host"];
+ if(!empty($URI_PARTS["port"]))
+ $this->port = $URI_PARTS["port"];
+ if($this->_connect($fp))
+ {
+ if($this->_isproxy)
+ {
+ // using proxy, send entire URI
+ $this->_httprequest($URI,$fp,$URI,$this->_submit_method,$this->_submit_type,$postdata);
+ }
+ else
+ {
+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
+ // no proxy, send only the path
+ $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata);
+ }
+
+ $this->_disconnect($fp);
+
+ if($this->_redirectaddr)
+ {
+ /* url was redirected, check if we've hit the max depth */
+ if($this->maxredirs > $this->_redirectdepth)
+ {
+ if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr))
+ $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]);
+
+ // only follow redirect if it's on this site, or offsiteok is true
+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
+ {
+ /* follow the redirect */
+ $this->_redirectdepth++;
+ $this->lastredirectaddr=$this->_redirectaddr;
+ if( strpos( $this->_redirectaddr, "?" ) > 0 )
+ $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get
+ else
+ $this->submit($this->_redirectaddr,$formvars, $formfiles);
+ }
+ }
+ }
+
+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
+ {
+ $frameurls = $this->_frameurls;
+ $this->_frameurls = array();
+
+ while(list(,$frameurl) = each($frameurls))
+ {
+ if($this->_framedepth < $this->maxframes)
+ {
+ $this->fetch($frameurl);
+ $this->_framedepth++;
+ }
+ else
+ break;
+ }
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ break;
+ case "https":
+ if(!$this->curl_path)
+ return false;
+ if(function_exists("is_executable"))
+ if (!is_executable($this->curl_path))
+ return false;
+ $this->host = $URI_PARTS["host"];
+ if(!empty($URI_PARTS["port"]))
+ $this->port = $URI_PARTS["port"];
+ if($this->_isproxy)
+ {
+ // using proxy, send entire URI
+ $this->_httpsrequest($URI, $URI, $this->_submit_method, $this->_submit_type, $postdata);
+ }
+ else
+ {
+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
+ // no proxy, send only the path
+ $this->_httpsrequest($path, $URI, $this->_submit_method, $this->_submit_type, $postdata);
+ }
+
+ if($this->_redirectaddr)
+ {
+ /* url was redirected, check if we've hit the max depth */
+ if($this->maxredirs > $this->_redirectdepth)
+ {
+ if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr))
+ $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]);
+
+ // only follow redirect if it's on this site, or offsiteok is true
+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
+ {
+ /* follow the redirect */
+ $this->_redirectdepth++;
+ $this->lastredirectaddr=$this->_redirectaddr;
+ if( strpos( $this->_redirectaddr, "?" ) > 0 )
+ $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get
+ else
+ $this->submit($this->_redirectaddr,$formvars, $formfiles);
+ }
+ }
+ }
+
+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
+ {
+ $frameurls = $this->_frameurls;
+ $this->_frameurls = array();
+
+ while(list(,$frameurl) = each($frameurls))
+ {
+ if($this->_framedepth < $this->maxframes)
+ {
+ $this->fetch($frameurl);
+ $this->_framedepth++;
+ }
+ else
+ break;
+ }
+ }
+ return true;
+ break;
+
+ default:
+ // not a valid protocol
+ $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n';
+ return false;
+ break;
+ }
+ return true;
+ }
+
+/*======================================================================*\
+ Function: fetchlinks
+ Purpose: fetch the links from a web page
+ Input: $URI where you are fetching from
+ Output: $this->results an array of the URLs
+\*======================================================================*/
+
+ function fetchlinks($URI)
+ {
+ if ($this->fetch($URI))
+ {
+ if($this->lastredirectaddr)
+ $URI = $this->lastredirectaddr;
+ if(is_array($this->results))
+ {
+ for($x=0;$x<count($this->results);$x++)
+ $this->results[$x] = $this->_striplinks($this->results[$x]);
+ }
+ else
+ $this->results = $this->_striplinks($this->results);
+
+ if($this->expandlinks)
+ $this->results = $this->_expandlinks($this->results, $URI);
+ return true;
+ }
+ else
+ return false;
+ }
+
+/*======================================================================*\
+ Function: fetchform
+ Purpose: fetch the form elements from a web page
+ Input: $URI where you are fetching from
+ Output: $this->results the resulting html form
+\*======================================================================*/
+
+ function fetchform($URI)
+ {
+
+ if ($this->fetch($URI))
+ {
+
+ if(is_array($this->results))
+ {
+ for($x=0;$x<count($this->results);$x++)
+ $this->results[$x] = $this->_stripform($this->results[$x]);
+ }
+ else
+ $this->results = $this->_stripform($this->results);
+
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+/*======================================================================*\
+ Function: fetchtext
+ Purpose: fetch the text from a web page, stripping the links
+ Input: $URI where you are fetching from
+ Output: $this->results the text from the web page
+\*======================================================================*/
+
+ function fetchtext($URI)
+ {
+ if($this->fetch($URI))
+ {
+ if(is_array($this->results))
+ {
+ for($x=0;$x<count($this->results);$x++)
+ $this->results[$x] = $this->_striptext($this->results[$x]);
+ }
+ else
+ $this->results = $this->_striptext($this->results);
+ return true;
+ }
+ else
+ return false;
+ }
+
+/*======================================================================*\
+ Function: submitlinks
+ Purpose: grab links from a form submission
+ Input: $URI where you are submitting from
+ Output: $this->results an array of the links from the post
+\*======================================================================*/
+
+ function submitlinks($URI, $formvars="", $formfiles="")
+ {
+ if($this->submit($URI,$formvars, $formfiles))
+ {
+ if($this->lastredirectaddr)
+ $URI = $this->lastredirectaddr;
+ if(is_array($this->results))
+ {
+ for($x=0;$x<count($this->results);$x++)
+ {
+ $this->results[$x] = $this->_striplinks($this->results[$x]);
+ if($this->expandlinks)
+ $this->results[$x] = $this->_expandlinks($this->results[$x],$URI);
+ }
+ }
+ else
+ {
+ $this->results = $this->_striplinks($this->results);
+ if($this->expandlinks)
+ $this->results = $this->_expandlinks($this->results,$URI);
+ }
+ return true;
+ }
+ else
+ return false;
+ }
+
+/*======================================================================*\
+ Function: submittext
+ Purpose: grab text from a form submission
+ Input: $URI where you are submitting from
+ Output: $this->results the text from the web page
+\*======================================================================*/
+
+ function submittext($URI, $formvars = "", $formfiles = "")
+ {
+ if($this->submit($URI,$formvars, $formfiles))
+ {
+ if($this->lastredirectaddr)
+ $URI = $this->lastredirectaddr;
+ if(is_array($this->results))
+ {
+ for($x=0;$x<count($this->results);$x++)
+ {
+ $this->results[$x] = $this->_striptext($this->results[$x]);
+ if($this->expandlinks)
+ $this->results[$x] = $this->_expandlinks($this->results[$x],$URI);
+ }
+ }
+ else
+ {
+ $this->results = $this->_striptext($this->results);
+ if($this->expandlinks)
+ $this->results = $this->_expandlinks($this->results,$URI);
+ }
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+
+/*======================================================================*\
+ Function: set_submit_multipart
+ Purpose: Set the form submission content type to
+ multipart/form-data
+\*======================================================================*/
+ function set_submit_multipart()
+ {
+ $this->_submit_type = "multipart/form-data";
+ }
+
+
+/*======================================================================*\
+ Function: set_submit_normal
+ Purpose: Set the form submission content type to
+ application/x-www-form-urlencoded
+\*======================================================================*/
+ function set_submit_normal()
+ {
+ $this->_submit_type = "application/x-www-form-urlencoded";
+ }
+
+
+
+
+/*======================================================================*\
+ Private functions
+\*======================================================================*/
+
+
+/*======================================================================*\
+ Function: _striplinks
+ Purpose: strip the hyperlinks from an html document
+ Input: $document document to strip.
+ Output: $match an array of the links
+\*======================================================================*/
+
+ function _striplinks($document)
+ {
+ preg_match_all("'<\s*a\s.*?href\s*=\s* # find <a href=
+ ([\"\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\s\>]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space
+ 'isx",$document,$links);
+
+
+ // catenate the non-empty matches from the conditional subpattern
+
+ while(list($key,$val) = each($links[2]))
+ {
+ if(!empty($val))
+ $match[] = $val;
+ }
+
+ while(list($key,$val) = each($links[3]))
+ {
+ if(!empty($val))
+ $match[] = $val;
+ }
+
+ // return the links
+ return $match;
+ }
+
+/*======================================================================*\
+ Function: _stripform
+ Purpose: strip the form elements from an html document
+ Input: $document document to strip.
+ Output: $match an array of the links
+\*======================================================================*/
+
+ function _stripform($document)
+ {
+ preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements);
+
+ // catenate the matches
+ $match = implode("\r\n",$elements[0]);
+
+ // return the links
+ return $match;
+ }
+
+
+
+/*======================================================================*\
+ Function: _striptext
+ Purpose: strip the text from an html document
+ Input: $document document to strip.
+ Output: $text the resulting text
+\*======================================================================*/
+
+ function _striptext($document)
+ {
+
+ // I didn't use preg eval (//e) since that is only available in PHP 4.0.
+ // so, list your entities one by one here. I included some of the
+ // more common ones.
+
+ $search = array("'<script[^>]*?>.*?</script>'si", // strip out javascript
+ "'<[\/\!]*?[^<>]*?>'si", // strip out html tags
+ "'([\r\n])[\s]+'", // strip out white space
+ "'&(quot|#34|#034|#x22);'i", // replace html entities
+ "'&(amp|#38|#038|#x26);'i", // added hexadecimal values
+ "'&(lt|#60|#060|#x3c);'i",
+ "'&(gt|#62|#062|#x3e);'i",
+ "'&(nbsp|#160|#xa0);'i",
+ "'&(iexcl|#161);'i",
+ "'&(cent|#162);'i",
+ "'&(pound|#163);'i",
+ "'&(copy|#169);'i",
+ "'&(reg|#174);'i",
+ "'&(deg|#176);'i",
+ "'&(#39|#039|#x27);'",
+ "'&(euro|#8364);'i", // europe
+ "'&a(uml|UML);'", // german
+ "'&o(uml|UML);'",
+ "'&u(uml|UML);'",
+ "'&A(uml|UML);'",
+ "'&O(uml|UML);'",
+ "'&U(uml|UML);'",
+ "'ß'i",
+ );
+ $replace = array( "",
+ "",
+ "\\1",
+ "\"",
+ "&",
+ "<",
+ ">",
+ " ",
+ chr(161),
+ chr(162),
+ chr(163),
+ chr(169),
+ chr(174),
+ chr(176),
+ chr(39),
+ chr(128),
+ "ä",
+ "ö",
+ "ü",
+ "Ä",
+ "Ö",
+ "Ü",
+ "ß",
+ );
+
+ $text = preg_replace($search,$replace,$document);
+
+ return $text;
+ }
+
+/*======================================================================*\
+ Function: _expandlinks
+ Purpose: expand each link into a fully qualified URL
+ Input: $links the links to qualify
+ $URI the full URI to get the base from
+ Output: $expandedLinks the expanded links
+\*======================================================================*/
+
+ function _expandlinks($links,$URI)
+ {
+
+ preg_match("/^[^\?]+/",$URI,$match);
+
+ $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]);
+ $match = preg_replace("|/$|","",$match);
+ $match_part = parse_url($match);
+ $match_root =
+ $match_part["scheme"]."://".$match_part["host"];
+
+ $search = array( "|^http://".preg_quote($this->host)."|i",
+ "|^(\/)|i",
+ "|^(?!http://)(?!mailto:)|i",
+ "|/\./|",
+ "|/[^\/]+/\.\./|"
+ );
+
+ $replace = array( "",
+ $match_root."/",
+ $match."/",
+ "/",
+ "/"
+ );
+
+ $expandedLinks = preg_replace($search,$replace,$links);
+
+ return $expandedLinks;
+ }
+
+/*======================================================================*\
+ Function: _httprequest
+ Purpose: go get the http data from the server
+ Input: $url the url to fetch
+ $fp the current open file pointer
+ $URI the full URI
+ $body body contents to send if any (POST)
+ Output:
+\*======================================================================*/
+
+ function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="")
+ {
+ $cookie_headers = '';
+ if($this->passcookies && $this->_redirectaddr)
+ $this->setcookies();
+
+ $URI_PARTS = parse_url($URI);
+ if(empty($url))
+ $url = "/";
+ $headers = $http_method." ".$url." ".$this->_httpversion."\r\n";
+ if(!empty($this->agent))
+ $headers .= "User-Agent: ".$this->agent."\r\n";
+ if(!empty($this->host) && !isset($this->rawheaders['Host'])) {
+ $headers .= "Host: ".$this->host;
+ if(!empty($this->port))
+ $headers .= ":".$this->port;
+ $headers .= "\r\n";
+ }
+ if(!empty($this->accept))
+ $headers .= "Accept: ".$this->accept."\r\n";
+ if(!empty($this->referer))
+ $headers .= "Referer: ".$this->referer."\r\n";
+ if(!empty($this->cookies))
+ {
+ if(!is_array($this->cookies))
+ $this->cookies = (array)$this->cookies;
+
+ reset($this->cookies);
+ if ( count($this->cookies) > 0 ) {
+ $cookie_headers .= 'Cookie: ';
+ foreach ( $this->cookies as $cookieKey => $cookieVal ) {
+ $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; ";
+ }
+ $headers .= substr($cookie_headers,0,-2) . "\r\n";
+ }
+ }
+ if(!empty($this->rawheaders))
+ {
+ if(!is_array($this->rawheaders))
+ $this->rawheaders = (array)$this->rawheaders;
+ while(list($headerKey,$headerVal) = each($this->rawheaders))
+ $headers .= $headerKey.": ".$headerVal."\r\n";
+ }
+ if(!empty($content_type)) {
+ $headers .= "Content-type: $content_type";
+ if ($content_type == "multipart/form-data")
+ $headers .= "; boundary=".$this->_mime_boundary;
+ $headers .= "\r\n";
+ }
+ if(!empty($body))
+ $headers .= "Content-length: ".strlen($body)."\r\n";
+ if(!empty($this->user) || !empty($this->pass))
+ $headers .= "Authorization: Basic ".base64_encode($this->user.":".$this->pass)."\r\n";
+
+ //add proxy auth headers
+ if(!empty($this->proxy_user))
+ $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass)."\r\n";
+
+
+ $headers .= "\r\n";
+
+ // set the read timeout if needed
+ if ($this->read_timeout > 0)
+ socket_set_timeout($fp, $this->read_timeout);
+ $this->timed_out = false;
+
+ fwrite($fp,$headers.$body,strlen($headers.$body));
+
+ $this->_redirectaddr = false;
+ unset($this->headers);
+
+ while($currentHeader = fgets($fp,$this->_maxlinelen))
+ {
+ if ($this->read_timeout > 0 && $this->_check_timeout($fp))
+ {
+ $this->status=-100;
+ return false;
+ }
+
+ if($currentHeader == "\r\n")
+ break;
+
+ // if a header begins with Location: or URI:, set the redirect
+ if(preg_match("/^(Location:|URI:)/i",$currentHeader))
+ {
+ // get URL portion of the redirect
+ preg_match("/^(Location:|URI:)[ ]+(.*)/i",chop($currentHeader),$matches);
+ // look for :// in the Location header to see if hostname is included
+ if(!preg_match("|\:\/\/|",$matches[2]))
+ {
+ // no host in the path, so prepend
+ $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port;
+ // eliminate double slash
+ if(!preg_match("|^/|",$matches[2]))
+ $this->_redirectaddr .= "/".$matches[2];
+ else
+ $this->_redirectaddr .= $matches[2];
+ }
+ else
+ $this->_redirectaddr = $matches[2];
+ }
+
+ if(preg_match("|^HTTP/|",$currentHeader))
+ {
+ if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status))
+ {
+ $this->status= $status[1];
+ }
+ $this->response_code = $currentHeader;
+ }
+
+ $this->headers[] = $currentHeader;
+ }
+
+ $results = '';
+ do {
+ $_data = fread($fp, $this->maxlength);
+ if (strlen($_data) == 0) {
+ break;
+ }
+ $results .= $_data;
+ } while(true);
+
+ if ($this->read_timeout > 0 && $this->_check_timeout($fp))
+ {
+ $this->status=-100;
+ return false;
+ }
+
+ // check if there is a a redirect meta tag
+
+ if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match))
+
+ {
+ $this->_redirectaddr = $this->_expandlinks($match[1],$URI);
+ }
+
+ // have we hit our frame depth and is there frame src to fetch?
+ if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match))
+ {
+ $this->results[] = $results;
+ for($x=0; $x<count($match[1]); $x++)
+ $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host);
+ }
+ // have we already fetched framed content?
+ elseif(is_array($this->results))
+ $this->results[] = $results;
+ // no framed content
+ else
+ $this->results = $results;
+
+ return true;
+ }
+
+/*======================================================================*\
+ Function: _httpsrequest
+ Purpose: go get the https data from the server using curl
+ Input: $url the url to fetch
+ $URI the full URI
+ $body body contents to send if any (POST)
+ Output:
+\*======================================================================*/
+
+ function _httpsrequest($url,$URI,$http_method,$content_type="",$body="")
+ {
+ if($this->passcookies && $this->_redirectaddr)
+ $this->setcookies();
+
+ $headers = array();
+
+ $URI_PARTS = parse_url($URI);
+ if(empty($url))
+ $url = "/";
+ // GET ... header not needed for curl
+ //$headers[] = $http_method." ".$url." ".$this->_httpversion;
+ if(!empty($this->agent))
+ $headers[] = "User-Agent: ".$this->agent;
+ if(!empty($this->host))
+ if(!empty($this->port))
+ $headers[] = "Host: ".$this->host.":".$this->port;
+ else
+ $headers[] = "Host: ".$this->host;
+ if(!empty($this->accept))
+ $headers[] = "Accept: ".$this->accept;
+ if(!empty($this->referer))
+ $headers[] = "Referer: ".$this->referer;
+ if(!empty($this->cookies))
+ {
+ if(!is_array($this->cookies))
+ $this->cookies = (array)$this->cookies;
+
+ reset($this->cookies);
+ if ( count($this->cookies) > 0 ) {
+ $cookie_str = 'Cookie: ';
+ foreach ( $this->cookies as $cookieKey => $cookieVal ) {
+ $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; ";
+ }
+ $headers[] = substr($cookie_str,0,-2);
+ }
+ }
+ if(!empty($this->rawheaders))
+ {
+ if(!is_array($this->rawheaders))
+ $this->rawheaders = (array)$this->rawheaders;
+ while(list($headerKey,$headerVal) = each($this->rawheaders))
+ $headers[] = $headerKey.": ".$headerVal;
+ }
+ if(!empty($content_type)) {
+ if ($content_type == "multipart/form-data")
+ $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary;
+ else
+ $headers[] = "Content-type: $content_type";
+ }
+ if(!empty($body))
+ $headers[] = "Content-length: ".strlen($body);
+ if(!empty($this->user) || !empty($this->pass))
+ $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass);
+
+ for($curr_header = 0; $curr_header < count($headers); $curr_header++) {
+ $safer_header = strtr( $headers[$curr_header], "\"", " " );
+ $cmdline_params .= " -H \"".$safer_header."\"";
+ }
+
+ if(!empty($body))
+ $cmdline_params .= " -d \"$body\"";
+
+ if($this->read_timeout > 0)
+ $cmdline_params .= " -m ".$this->read_timeout;
+
+ $headerfile = tempnam($temp_dir, "sno");
+
+ $safer_URI = strtr( $URI, "\"", " " ); // strip quotes from the URI to avoid shell access
+ exec($this->curl_path." -D \"$headerfile\"".$cmdline_params." \"".$safer_URI."\"",$results,$return);
+
+ if($return)
+ {
+ $this->error = "Error: cURL could not retrieve the document, error $return.";
+ return false;
+ }
+
+
+ $results = implode("\r\n",$results);
+
+ $result_headers = file("$headerfile");
+
+ $this->_redirectaddr = false;
+ unset($this->headers);
+
+ for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++)
+ {
+
+ // if a header begins with Location: or URI:, set the redirect
+ if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader]))
+ {
+ // get URL portion of the redirect
+ preg_match("/^(Location: |URI:)\s+(.*)/",chop($result_headers[$currentHeader]),$matches);
+ // look for :// in the Location header to see if hostname is included
+ if(!preg_match("|\:\/\/|",$matches[2]))
+ {
+ // no host in the path, so prepend
+ $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port;
+ // eliminate double slash
+ if(!preg_match("|^/|",$matches[2]))
+ $this->_redirectaddr .= "/".$matches[2];
+ else
+ $this->_redirectaddr .= $matches[2];
+ }
+ else
+ $this->_redirectaddr = $matches[2];
+ }
+
+ if(preg_match("|^HTTP/|",$result_headers[$currentHeader]))
+ $this->response_code = $result_headers[$currentHeader];
+
+ $this->headers[] = $result_headers[$currentHeader];
+ }
+
+ // check if there is a a redirect meta tag
+
+ if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match))
+ {
+ $this->_redirectaddr = $this->_expandlinks($match[1],$URI);
+ }
+
+ // have we hit our frame depth and is there frame src to fetch?
+ if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match))
+ {
+ $this->results[] = $results;
+ for($x=0; $x<count($match[1]); $x++)
+ $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host);
+ }
+ // have we already fetched framed content?
+ elseif(is_array($this->results))
+ $this->results[] = $results;
+ // no framed content
+ else
+ $this->results = $results;
+
+ unlink("$headerfile");
+
+ return true;
+ }
+
+/*======================================================================*\
+ Function: setcookies()
+ Purpose: set cookies for a redirection
+\*======================================================================*/
+
+ function setcookies()
+ {
+ for($x=0; $x<count($this->headers); $x++)
+ {
+ if(preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x],$match))
+ $this->cookies[$match[1]] = urldecode($match[2]);
+ }
+ }
+
+
+/*======================================================================*\
+ Function: _check_timeout
+ Purpose: checks whether timeout has occurred
+ Input: $fp file pointer
+\*======================================================================*/
+
+ function _check_timeout($fp)
+ {
+ if ($this->read_timeout > 0) {
+ $fp_status = socket_get_status($fp);
+ if ($fp_status["timed_out"]) {
+ $this->timed_out = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+/*======================================================================*\
+ Function: _connect
+ Purpose: make a socket connection
+ Input: $fp file pointer
+\*======================================================================*/
+
+ function _connect(&$fp)
+ {
+ if(!empty($this->proxy_host) && !empty($this->proxy_port))
+ {
+ $this->_isproxy = true;
+
+ $host = $this->proxy_host;
+ $port = $this->proxy_port;
+ }
+ else
+ {
+ $host = $this->host;
+ $port = $this->port;
+ }
+
+ $this->status = 0;
+
+ if($fp = fsockopen(
+ $host,
+ $port,
+ $errno,
+ $errstr,
+ $this->_fp_timeout
+ ))
+ {
+ // socket connection succeeded
+
+ return true;
+ }
+ else
+ {
+ // socket connection failed
+ $this->status = $errno;
+ switch($errno)
+ {
+ case -3:
+ $this->error="socket creation failed (-3)";
+ case -4:
+ $this->error="dns lookup failure (-4)";
+ case -5:
+ $this->error="connection refused or timed out (-5)";
+ default:
+ $this->error="connection failed (".$errno.")";
+ }
+ return false;
+ }
+ }
+/*======================================================================*\
+ Function: _disconnect
+ Purpose: disconnect a socket connection
+ Input: $fp file pointer
+\*======================================================================*/
+
+ function _disconnect($fp)
+ {
+ return(fclose($fp));
+ }
+
+
+/*======================================================================*\
+ Function: _prepare_post_body
+ Purpose: Prepare post body according to encoding type
+ Input: $formvars - form variables
+ $formfiles - form upload files
+ Output: post body
+\*======================================================================*/
+
+ function _prepare_post_body($formvars, $formfiles)
+ {
+ settype($formvars, "array");
+ settype($formfiles, "array");
+ $postdata = '';
+
+ if (count($formvars) == 0 && count($formfiles) == 0)
+ return;
+
+ switch ($this->_submit_type) {
+ case "application/x-www-form-urlencoded":
+ reset($formvars);
+ while(list($key,$val) = each($formvars)) {
+ if (is_array($val) || is_object($val)) {
+ while (list($cur_key, $cur_val) = each($val)) {
+ $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&";
+ }
+ } else
+ $postdata .= urlencode($key)."=".urlencode($val)."&";
+ }
+ break;
+
+ case "multipart/form-data":
+ $this->_mime_boundary = "Snoopy".md5(uniqid(microtime()));
+
+ reset($formvars);
+ while(list($key,$val) = each($formvars)) {
+ if (is_array($val) || is_object($val)) {
+ while (list($cur_key, $cur_val) = each($val)) {
+ $postdata .= "--".$this->_mime_boundary."\r\n";
+ $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n";
+ $postdata .= "$cur_val\r\n";
+ }
+ } else {
+ $postdata .= "--".$this->_mime_boundary."\r\n";
+ $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";
+ $postdata .= "$val\r\n";
+ }
+ }
+
+ reset($formfiles);
+ while (list($field_name, $file_names) = each($formfiles)) {
+ settype($file_names, "array");
+ while (list(, $file_name) = each($file_names)) {
+ if (!is_readable($file_name)) continue;
+
+ $fp = fopen($file_name, "r");
+ $file_content = fread($fp, filesize($file_name));
+ fclose($fp);
+ $base_name = basename($file_name);
+
+ $postdata .= "--".$this->_mime_boundary."\r\n";
+ $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n";
+ $postdata .= "$file_content\r\n";
+ }
+ }
+ $postdata .= "--".$this->_mime_boundary."--\r\n";
+ break;
+ }
+
+ return $postdata;
+ }
+}
+
+?>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Key gateway class for XML_Feed_Parser package
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL
+ * @version CVS: $Id: Parser.php,v 1.24 2006/08/15 13:04:00 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * XML_Feed_Parser_Type is an abstract class required by all of our
+ * feed types. It makes sense to load it here to keep the other files
+ * clean.
+ */
+require_once 'XML/Feed/Parser/Type.php';
+
+/**
+ * We will throw exceptions when errors occur.
+ */
+require_once 'XML/Feed/Parser/Exception.php';
+
+/**
+ * This is the core of the XML_Feed_Parser package. It identifies feed types
+ * and abstracts access to them. It is an iterator, allowing for easy access
+ * to the entire feed.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser implements Iterator
+{
+ /**
+ * This is where we hold the feed object
+ * @var Object
+ */
+ private $feed;
+
+ /**
+ * To allow for extensions, we make a public reference to the feed model
+ * @var DOMDocument
+ */
+ public $model;
+
+ /**
+ * A map between entry ID and offset
+ * @var array
+ */
+ protected $idMappings = array();
+
+ /**
+ * A storage space for Namespace URIs.
+ * @var array
+ */
+ private $feedNamespaces = array(
+ 'rss2' => array(
+ 'http://backend.userland.com/rss',
+ 'http://backend.userland.com/rss2',
+ 'http://blogs.law.harvard.edu/tech/rss'));
+ /**
+ * Detects feed types and instantiate appropriate objects.
+ *
+ * Our constructor takes care of detecting feed types and instantiating
+ * appropriate classes. For now we're going to treat Atom 0.3 as Atom 1.0
+ * but raise a warning. I do not intend to introduce full support for
+ * Atom 0.3 as it has been deprecated, but others are welcome to.
+ *
+ * @param string $feed XML serialization of the feed
+ * @param bool $strict Whether or not to validate the feed
+ * @param bool $suppressWarnings Trigger errors for deprecated feed types?
+ * @param bool $tidy Whether or not to try and use the tidy library on input
+ */
+ function __construct($feed, $strict = false, $suppressWarnings = false, $tidy = false)
+ {
+ $this->model = new DOMDocument;
+ if (! $this->model->loadXML($feed)) {
+ if (extension_loaded('tidy') && $tidy) {
+ $tidy = new tidy;
+ $tidy->parseString($feed,
+ array('input-xml' => true, 'output-xml' => true));
+ $tidy->cleanRepair();
+ if (! $this->model->loadXML((string) $tidy)) {
+ throw new XML_Feed_Parser_Exception('Invalid input: this is not ' .
+ 'valid XML');
+ }
+ } else {
+ throw new XML_Feed_Parser_Exception('Invalid input: this is not valid XML');
+ }
+
+ }
+
+ /* detect feed type */
+ $doc_element = $this->model->documentElement;
+ $error = false;
+
+ switch (true) {
+ case ($doc_element->namespaceURI == 'http://www.w3.org/2005/Atom'):
+ require_once 'XML/Feed/Parser/Atom.php';
+ require_once 'XML/Feed/Parser/AtomElement.php';
+ $class = 'XML_Feed_Parser_Atom';
+ break;
+ case ($doc_element->namespaceURI == 'http://purl.org/atom/ns#'):
+ require_once 'XML/Feed/Parser/Atom.php';
+ require_once 'XML/Feed/Parser/AtomElement.php';
+ $class = 'XML_Feed_Parser_Atom';
+ $error = 'Atom 0.3 deprecated, using 1.0 parser which won\'t provide ' .
+ 'all options';
+ break;
+ case ($doc_element->namespaceURI == 'http://purl.org/rss/1.0/' ||
+ ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
+ && $doc_element->childNodes->item(1)->namespaceURI ==
+ 'http://purl.org/rss/1.0/')):
+ require_once 'XML/Feed/Parser/RSS1.php';
+ require_once 'XML/Feed/Parser/RSS1Element.php';
+ $class = 'XML_Feed_Parser_RSS1';
+ break;
+ case ($doc_element->namespaceURI == 'http://purl.org/rss/1.1/' ||
+ ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
+ && $doc_element->childNodes->item(1)->namespaceURI ==
+ 'http://purl.org/rss/1.1/')):
+ require_once 'XML/Feed/Parser/RSS11.php';
+ require_once 'XML/Feed/Parser/RSS11Element.php';
+ $class = 'XML_Feed_Parser_RSS11';
+ break;
+ case (($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
+ && $doc_element->childNodes->item(1)->namespaceURI ==
+ 'http://my.netscape.com/rdf/simple/0.9/') ||
+ $doc_element->namespaceURI == 'http://my.netscape.com/rdf/simple/0.9/'):
+ require_once 'XML/Feed/Parser/RSS09.php';
+ require_once 'XML/Feed/Parser/RSS09Element.php';
+ $class = 'XML_Feed_Parser_RSS09';
+ break;
+ case ($doc_element->tagName == 'rss' and
+ $doc_element->hasAttribute('version') &&
+ $doc_element->getAttribute('version') == 0.91):
+ $error = 'RSS 0.91 has been superceded by RSS2.0. Using RSS2.0 parser.';
+ require_once 'XML/Feed/Parser/RSS2.php';
+ require_once 'XML/Feed/Parser/RSS2Element.php';
+ $class = 'XML_Feed_Parser_RSS2';
+ break;
+ case ($doc_element->tagName == 'rss' and
+ $doc_element->hasAttribute('version') &&
+ $doc_element->getAttribute('version') == 0.92):
+ $error = 'RSS 0.92 has been superceded by RSS2.0. Using RSS2.0 parser.';
+ require_once 'XML/Feed/Parser/RSS2.php';
+ require_once 'XML/Feed/Parser/RSS2Element.php';
+ $class = 'XML_Feed_Parser_RSS2';
+ break;
+ case (in_array($doc_element->namespaceURI, $this->feedNamespaces['rss2'])
+ || $doc_element->tagName == 'rss'):
+ if (! $doc_element->hasAttribute('version') ||
+ $doc_element->getAttribute('version') != 2) {
+ $error = 'RSS version not specified. Parsing as RSS2.0';
+ }
+ require_once 'XML/Feed/Parser/RSS2.php';
+ require_once 'XML/Feed/Parser/RSS2Element.php';
+ $class = 'XML_Feed_Parser_RSS2';
+ break;
+ default:
+ throw new XML_Feed_Parser_Exception('Feed type unknown');
+ break;
+ }
+
+ if (! $suppressWarnings && ! empty($error)) {
+ trigger_error($error, E_USER_WARNING);
+ }
+
+ /* Instantiate feed object */
+ $this->feed = new $class($this->model, $strict);
+ }
+
+ /**
+ * Proxy to allow feed element names to be used as method names
+ *
+ * For top-level feed elements we will provide access using methods or
+ * attributes. This function simply passes on a request to the appropriate
+ * feed type object.
+ *
+ * @param string $call - the method being called
+ * @param array $attributes
+ */
+ function __call($call, $attributes)
+ {
+ $attributes = array_pad($attributes, 5, false);
+ list($a, $b, $c, $d, $e) = $attributes;
+ return $this->feed->$call($a, $b, $c, $d, $e);
+ }
+
+ /**
+ * Proxy to allow feed element names to be used as attribute names
+ *
+ * To allow variable-like access to feed-level data we use this
+ * method. It simply passes along to __call() which in turn passes
+ * along to the relevant object.
+ *
+ * @param string $val - the name of the variable required
+ */
+ function __get($val)
+ {
+ return $this->feed->$val;
+ }
+
+ /**
+ * Provides iteration functionality.
+ *
+ * Of course we must be able to iterate... This function simply increases
+ * our internal counter.
+ */
+ function next()
+ {
+ if (isset($this->current_item) &&
+ $this->current_item <= $this->feed->numberEntries - 1) {
+ ++$this->current_item;
+ } else if (! isset($this->current_item)) {
+ $this->current_item = 0;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return XML_Feed_Type object for current element
+ *
+ * @return XML_Feed_Parser_Type Object
+ */
+ function current()
+ {
+ return $this->getEntryByOffset($this->current_item);
+ }
+
+ /**
+ * For iteration -- returns the key for the current stage in the array.
+ *
+ * @return int
+ */
+ function key()
+ {
+ return $this->current_item;
+ }
+
+ /**
+ * For iteration -- tells whether we have reached the
+ * end.
+ *
+ * @return bool
+ */
+ function valid()
+ {
+ return $this->current_item < $this->feed->numberEntries;
+ }
+
+ /**
+ * For iteration -- resets the internal counter to the beginning.
+ */
+ function rewind()
+ {
+ $this->current_item = 0;
+ }
+
+ /**
+ * Provides access to entries by ID if one is specified in the source feed.
+ *
+ * As well as allowing the items to be iterated over we want to allow
+ * users to be able to access a specific entry. This is one of two ways of
+ * doing that, the other being by offset. This method can be quite slow
+ * if dealing with a large feed that hasn't yet been processed as it
+ * instantiates objects for every entry until it finds the one needed.
+ *
+ * @param string $id Valid ID for the given feed format
+ * @return XML_Feed_Parser_Type|false
+ */
+ function getEntryById($id)
+ {
+ if (isset($this->idMappings[$id])) {
+ return $this->getEntryByOffset($this->idMappings[$id]);
+ }
+
+ /*
+ * Since we have not yet encountered that ID, let's go through all the
+ * remaining entries in order till we find it.
+ * This is a fairly slow implementation, but it should work.
+ */
+ return $this->feed->getEntryById($id);
+ }
+
+ /**
+ * Retrieve entry by numeric offset, starting from zero.
+ *
+ * As well as allowing the items to be iterated over we want to allow
+ * users to be able to access a specific entry. This is one of two ways of
+ * doing that, the other being by ID.
+ *
+ * @param int $offset The position of the entry within the feed, starting from 0
+ * @return XML_Feed_Parser_Type|false
+ */
+ function getEntryByOffset($offset)
+ {
+ if ($offset < $this->feed->numberEntries) {
+ if (isset($this->feed->entries[$offset])) {
+ return $this->feed->entries[$offset];
+ } else {
+ try {
+ $this->feed->getEntryByOffset($offset);
+ } catch (Exception $e) {
+ return false;
+ }
+ $id = $this->feed->entries[$offset]->getID();
+ $this->idMappings[$id] = $offset;
+ return $this->feed->entries[$offset];
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve version details from feed type class.
+ *
+ * @return void
+ * @author James Stewart
+ */
+ function version()
+ {
+ return $this->feed->version;
+ }
+
+ /**
+ * Returns a string representation of the feed.
+ *
+ * @return String
+ **/
+ function __toString()
+ {
+ return $this->feed->__toString();
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Atom feed class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: Atom.php,v 1.25 2007/03/26 12:49:05 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+*/
+
+/**
+ * This is the class that determines how we manage Atom 1.0 feeds
+ *
+ * How we deal with constructs:
+ * date - return as unix datetime for use with the 'date' function unless specified otherwise
+ * text - return as is. optional parameter will give access to attributes
+ * person - defaults to name, but parameter based access
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_Atom extends XML_Feed_Parser_Type
+{
+ /**
+ * The URI of the RelaxNG schema used to (optionally) validate the feed
+ * @var string
+ */
+ private $relax = 'atom.rnc';
+
+ /**
+ * We're likely to use XPath, so let's keep it global
+ * @var DOMXPath
+ */
+ public $xpath;
+
+ /**
+ * When performing XPath queries we will use this prefix
+ * @var string
+ */
+ private $xpathPrefix = '//';
+
+ /**
+ * The feed type we are parsing
+ * @var string
+ */
+ public $version = 'Atom 1.0';
+
+ /**
+ * The class used to represent individual items
+ * @var string
+ */
+ protected $itemClass = 'XML_Feed_Parser_AtomElement';
+
+ /**
+ * The element containing entries
+ * @var string
+ */
+ protected $itemElement = 'entry';
+
+ /**
+ * Here we map those elements we're not going to handle individually
+ * to the constructs they are. The optional second parameter in the array
+ * tells the parser whether to 'fall back' (not apt. at the feed level) or
+ * fail if the element is missing. If the parameter is not set, the function
+ * will simply return false and leave it to the client to decide what to do.
+ * @var array
+ */
+ protected $map = array(
+ 'author' => array('Person'),
+ 'contributor' => array('Person'),
+ 'icon' => array('Text'),
+ 'logo' => array('Text'),
+ 'id' => array('Text', 'fail'),
+ 'rights' => array('Text'),
+ 'subtitle' => array('Text'),
+ 'title' => array('Text', 'fail'),
+ 'updated' => array('Date', 'fail'),
+ 'link' => array('Link'),
+ 'generator' => array('Text'),
+ 'category' => array('Category'));
+
+ /**
+ * Here we provide a few mappings for those very special circumstances in
+ * which it makes sense to map back to the RSS2 spec. Key is RSS2 version
+ * value is an array consisting of the equivalent in atom and any attributes
+ * needed to make the mapping.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'guid' => array('id'),
+ 'links' => array('link'),
+ 'tags' => array('category'),
+ 'contributors' => array('contributor'));
+
+ /**
+ * Our constructor does nothing more than its parent.
+ *
+ * @param DOMDocument $xml A DOM object representing the feed
+ * @param bool (optional) $string Whether or not to validate this feed
+ */
+ function __construct(DOMDocument $model, $strict = false)
+ {
+ $this->model = $model;
+
+ if ($strict) {
+ if (! $this->model->relaxNGValidateSource($this->relax)) {
+ throw new XML_Feed_Parser_Exception('Failed required validation');
+ }
+ }
+
+ $this->xpath = new DOMXPath($this->model);
+ $this->xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
+ $this->numberEntries = $this->count('entry');
+ }
+
+ /**
+ * Implement retrieval of an entry based on its ID for atom feeds.
+ *
+ * This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate
+ * is available, we also use that to store a reference to the entry in the array
+ * used by getEntryByOffset so that method does not have to seek out the entry
+ * if it's requested that way.
+ *
+ * @param string $id any valid Atom ID.
+ * @return XML_Feed_Parser_AtomElement
+ */
+ function getEntryById($id)
+ {
+ if (isset($this->idMappings[$id])) {
+ return $this->entries[$this->idMappings[$id]];
+ }
+
+ $entries = $this->xpath->query("//atom:entry[atom:id='$id']");
+
+ if ($entries->length > 0) {
+ $xmlBase = $entries->item(0)->baseURI;
+ $entry = new $this->itemElement($entries->item(0), $this, $xmlBase);
+
+ if (in_array('evaluate', get_class_methods($this->xpath))) {
+ $offset = $this->xpath->evaluate("count(preceding-sibling::atom:entry)", $entries->item(0));
+ $this->entries[$offset] = $entry;
+ }
+
+ $this->idMappings[$id] = $entry;
+
+ return $entry;
+ }
+
+ }
+
+ /**
+ * Retrieves data from a person construct.
+ *
+ * Get a person construct. We default to the 'name' element but allow
+ * access to any of the elements.
+ *
+ * @param string $method The name of the person construct we want
+ * @param array $arguments An array which we hope gives a 'param'
+ * @return string|false
+ */
+ protected function getPerson($method, $arguments)
+ {
+ $offset = empty($arguments[0]) ? 0 : $arguments[0];
+ $parameter = empty($arguments[1]['param']) ? 'name' : $arguments[1]['param'];
+ $section = $this->model->getElementsByTagName($method);
+
+ if ($parameter == 'url') {
+ $parameter = 'uri';
+ }
+
+ if ($section->length <= $offset) {
+ return false;
+ }
+
+ $param = $section->item($offset)->getElementsByTagName($parameter);
+ if ($param->length == 0) {
+ return false;
+ }
+ return $param->item(0)->nodeValue;
+ }
+
+ /**
+ * Retrieves an element's content where that content is a text construct.
+ *
+ * Get a text construct. When calling this method, the two arguments
+ * allowed are 'offset' and 'attribute', so $parser->subtitle() would
+ * return the content of the element, while $parser->subtitle(false, 'type')
+ * would return the value of the type attribute.
+ *
+ * @todo Clarify overlap with getContent()
+ * @param string $method The name of the text construct we want
+ * @param array $arguments An array which we hope gives a 'param'
+ * @return string
+ */
+ protected function getText($method, $arguments)
+ {
+ $offset = empty($arguments[0]) ? 0: $arguments[0];
+ $attribute = empty($arguments[1]) ? false : $arguments[1];
+ $tags = $this->model->getElementsByTagName($method);
+
+ if ($tags->length <= $offset) {
+ return false;
+ }
+
+ $content = $tags->item($offset);
+
+ if (! $content->hasAttribute('type')) {
+ $content->setAttribute('type', 'text');
+ }
+ $type = $content->getAttribute('type');
+
+ if (! empty($attribute) and
+ ! ($method == 'generator' and $attribute == 'name')) {
+ if ($content->hasAttribute($attribute)) {
+ return $content->getAttribute($attribute);
+ } else if ($attribute == 'href' and $content->hasAttribute('uri')) {
+ return $content->getAttribute('uri');
+ }
+ return false;
+ }
+ return $this->parseTextConstruct($content);
+ }
+
+ /**
+ * Extract content appropriately from atom text constructs
+ *
+ * Because of different rules applied to the content element and other text
+ * constructs, they are deployed as separate functions, but they share quite
+ * a bit of processing. This method performs the core common process, which is
+ * to apply the rules for different mime types in order to extract the content.
+ *
+ * @param DOMNode $content the text construct node to be parsed
+ * @return String
+ * @author James Stewart
+ **/
+ protected function parseTextConstruct(DOMNode $content)
+ {
+ if ($content->hasAttribute('type')) {
+ $type = $content->getAttribute('type');
+ } else {
+ $type = 'text';
+ }
+
+ if (strpos($type, 'text/') === 0) {
+ $type = 'text';
+ }
+ switch ($type) {
+ case 'text':
+ return $content->nodeValue;
+ break;
+ case 'html':
+ return str_replace('<', '<', $content->nodeValue);
+ break;
+ case 'xhtml':
+ $container = $content->getElementsByTagName('div');
+ if ($container->length == 0) {
+ return false;
+ }
+ $contents = $container->item(0);
+ if ($contents->hasChildNodes()) {
+ /* Iterate through, applying xml:base and store the result */
+ $result = '';
+ foreach ($contents->childNodes as $node) {
+ $result .= $this->traverseNode($node);
+ }
+ return utf8_decode($result);
+ }
+ break;
+ case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i', $type) > 0:
+ return $content;
+ break;
+ case 'application/octet-stream':
+ default:
+ return base64_decode(trim($content->nodeValue));
+ break;
+ }
+ return false;
+ }
+ /**
+ * Get a category from the entry.
+ *
+ * A feed or entry can have any number of categories. A category can have the
+ * attributes term, scheme and label.
+ *
+ * @param string $method The name of the text construct we want
+ * @param array $arguments An array which we hope gives a 'param'
+ * @return string
+ */
+ function getCategory($method, $arguments)
+ {
+ $offset = empty($arguments[0]) ? 0: $arguments[0];
+ $attribute = empty($arguments[1]) ? 'term' : $arguments[1];
+ $categories = $this->model->getElementsByTagName('category');
+ if ($categories->length <= $offset) {
+ $category = $categories->item($offset);
+ if ($category->hasAttribute($attribute)) {
+ return $category->getAttribute($attribute);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This element must be present at least once with rel="feed". This element may be
+ * present any number of further times so long as there is no clash. If no 'rel' is
+ * present and we're asked for one, we follow the example of the Universal Feed
+ * Parser and presume 'alternate'.
+ *
+ * @param int $offset the position of the link within the container
+ * @param string $attribute the attribute name required
+ * @param array an array of attributes to search by
+ * @return string the value of the attribute
+ */
+ function getLink($offset = 0, $attribute = 'href', $params = false)
+ {
+ if (is_array($params) and !empty($params)) {
+ $terms = array();
+ $alt_predicate = '';
+ $other_predicate = '';
+
+ foreach ($params as $key => $value) {
+ if ($key == 'rel' && $value == 'alternate') {
+ $alt_predicate = '[not(@rel) or @rel="alternate"]';
+ } else {
+ $terms[] = "@$key='$value'";
+ }
+ }
+ if (!empty($terms)) {
+ $other_predicate = '[' . join(' and ', $terms) . ']';
+ }
+ $query = $this->xpathPrefix . 'atom:link' . $alt_predicate . $other_predicate;
+ $links = $this->xpath->query($query);
+ } else {
+ $links = $this->model->getElementsByTagName('link');
+ }
+ if ($links->length > $offset) {
+ if ($links->item($offset)->hasAttribute($attribute)) {
+ $value = $links->item($offset)->getAttribute($attribute);
+ if ($attribute == 'href') {
+ $value = $this->addBase($value, $links->item($offset));
+ }
+ return $value;
+ } else if ($attribute == 'rel') {
+ return 'alternate';
+ }
+ }
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * AtomElement class for XML_Feed_Parser package
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: AtomElement.php,v 1.19 2007/03/26 12:43:11 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This class provides support for atom entries. It will usually be called by
+ * XML_Feed_Parser_Atom with which it shares many methods.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_AtomElement extends XML_Feed_Parser_Atom
+{
+ /**
+ * This will be a reference to the parent object for when we want
+ * to use a 'fallback' rule
+ * @var XML_Feed_Parser_Atom
+ */
+ protected $parent;
+
+ /**
+ * When performing XPath queries we will use this prefix
+ * @var string
+ */
+ private $xpathPrefix = '';
+
+ /**
+ * xml:base values inherited by the element
+ * @var string
+ */
+ protected $xmlBase;
+
+ /**
+ * Here we provide a few mappings for those very special circumstances in
+ * which it makes sense to map back to the RSS2 spec or to manage other
+ * compatibilities (eg. with the Univeral Feed Parser). Key is the other version's
+ * name for the command, value is an array consisting of the equivalent in our atom
+ * api and any attributes needed to make the mapping.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'guid' => array('id'),
+ 'links' => array('link'),
+ 'tags' => array('category'),
+ 'contributors' => array('contributor'));
+
+ /**
+ * Our specific element map
+ * @var array
+ */
+ protected $map = array(
+ 'author' => array('Person', 'fallback'),
+ 'contributor' => array('Person'),
+ 'id' => array('Text', 'fail'),
+ 'published' => array('Date'),
+ 'updated' => array('Date', 'fail'),
+ 'title' => array('Text', 'fail'),
+ 'rights' => array('Text', 'fallback'),
+ 'summary' => array('Text'),
+ 'content' => array('Content'),
+ 'link' => array('Link'),
+ 'enclosure' => array('Enclosure'),
+ 'category' => array('Category'));
+
+ /**
+ * Store useful information for later.
+ *
+ * @param DOMElement $element - this item as a DOM element
+ * @param XML_Feed_Parser_Atom $parent - the feed of which this is a member
+ */
+ function __construct(DOMElement $element, $parent, $xmlBase = '')
+ {
+ $this->model = $element;
+ $this->parent = $parent;
+ $this->xmlBase = $xmlBase;
+ $this->xpathPrefix = "//atom:entry[atom:id='" . $this->id . "']/";
+ $this->xpath = $this->parent->xpath;
+ }
+
+ /**
+ * Provides access to specific aspects of the author data for an atom entry
+ *
+ * Author data at the entry level is more complex than at the feed level.
+ * If atom:author is not present for the entry we need to look for it in
+ * an atom:source child of the atom:entry. If it's not there either, then
+ * we look to the parent for data.
+ *
+ * @param array
+ * @return string
+ */
+ function getAuthor($arguments)
+ {
+ /* Find out which part of the author data we're looking for */
+ if (isset($arguments['param'])) {
+ $parameter = $arguments['param'];
+ } else {
+ $parameter = 'name';
+ }
+
+ $test = $this->model->getElementsByTagName('author');
+ if ($test->length > 0) {
+ $item = $test->item(0);
+ return $item->getElementsByTagName($parameter)->item(0)->nodeValue;
+ }
+
+ $source = $this->model->getElementsByTagName('source');
+ if ($source->length > 0) {
+ $test = $this->model->getElementsByTagName('author');
+ if ($test->length > 0) {
+ $item = $test->item(0);
+ return $item->getElementsByTagName($parameter)->item(0)->nodeValue;
+ }
+ }
+ return $this->parent->getAuthor($arguments);
+ }
+
+ /**
+ * Returns the content of the content element or info on a specific attribute
+ *
+ * This element may or may not be present. It cannot be present more than
+ * once. It may have a 'src' attribute, in which case there's no content
+ * If not present, then the entry must have link with rel="alternate".
+ * If there is content we return it, if not and there's a 'src' attribute
+ * we return the value of that instead. The method can take an 'attribute'
+ * argument, in which case we return the value of that attribute if present.
+ * eg. $item->content("type") will return the type of the content. It is
+ * recommended that all users check the type before getting the content to
+ * ensure that their script is capable of handling the type of returned data.
+ * (data carried in the content element can be either 'text', 'html', 'xhtml',
+ * or any standard MIME type).
+ *
+ * @return string|false
+ */
+ protected function getContent($method, $arguments = array())
+ {
+ $attribute = empty($arguments[0]) ? false : $arguments[0];
+ $tags = $this->model->getElementsByTagName('content');
+
+ if ($tags->length == 0) {
+ return false;
+ }
+
+ $content = $tags->item(0);
+
+ if (! $content->hasAttribute('type')) {
+ $content->setAttribute('type', 'text');
+ }
+ if (! empty($attribute)) {
+ return $content->getAttribute($attribute);
+ }
+
+ $type = $content->getAttribute('type');
+
+ if (! empty($attribute)) {
+ if ($content->hasAttribute($attribute))
+ {
+ return $content->getAttribute($attribute);
+ }
+ return false;
+ }
+
+ if ($content->hasAttribute('src')) {
+ return $content->getAttribute('src');
+ }
+
+ return $this->parseTextConstruct($content);
+ }
+
+ /**
+ * For compatibility, this method provides a mapping to access enclosures.
+ *
+ * The Atom spec doesn't provide for an enclosure element, but it is
+ * generally supported using the link element with rel='enclosure'.
+ *
+ * @param string $method - for compatibility with our __call usage
+ * @param array $arguments - for compatibility with our __call usage
+ * @return array|false
+ */
+ function getEnclosure($method, $arguments = array())
+ {
+ $offset = isset($arguments[0]) ? $arguments[0] : 0;
+ $query = "//atom:entry[atom:id='" . $this->getText('id', false) .
+ "']/atom:link[@rel='enclosure']";
+
+ $encs = $this->parent->xpath->query($query);
+ if ($encs->length > $offset) {
+ try {
+ if (! $encs->item($offset)->hasAttribute('href')) {
+ return false;
+ }
+ $attrs = $encs->item($offset)->attributes;
+ $length = $encs->item($offset)->hasAttribute('length') ?
+ $encs->item($offset)->getAttribute('length') : false;
+ return array(
+ 'url' => $attrs->getNamedItem('href')->value,
+ 'type' => $attrs->getNamedItem('type')->value,
+ 'length' => $length);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get details of this entry's source, if available/relevant
+ *
+ * Where an atom:entry is taken from another feed then the aggregator
+ * is supposed to include an atom:source element which replicates at least
+ * the atom:id, atom:title, and atom:updated metadata from the original
+ * feed. Atom:source therefore has a very similar structure to atom:feed
+ * and if we find it we will return it as an XML_Feed_Parser_Atom object.
+ *
+ * @return XML_Feed_Parser_Atom|false
+ */
+ function getSource()
+ {
+ $test = $this->model->getElementsByTagName('source');
+ if ($test->length == 0) {
+ return false;
+ }
+ $source = new XML_Feed_Parser_Atom($test->item(0));
+ }
+
+ /**
+ * Get the entry as an XML string
+ *
+ * Return an XML serialization of the feed, should it be required. Most
+ * users however, will already have a serialization that they used when
+ * instantiating the object.
+ *
+ * @return string XML serialization of element
+ */
+ function __toString()
+ {
+ $simple = simplexml_import_dom($this->model);
+ return $simple->asXML();
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Keeps the exception class for XML_Feed_Parser.
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL
+ * @version CVS: $Id: Exception.php,v 1.3 2005/11/07 01:52:35 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * We are extending PEAR_Exception
+ */
+require_once 'PEAR/Exception.php';
+
+/**
+ * XML_Feed_Parser_Exception is a simple extension of PEAR_Exception, existing
+ * to help with identification of the source of exceptions.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_Exception extends PEAR_Exception
+{
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * RSS0.9 class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS09.php,v 1.5 2006/07/26 21:18:46 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This class handles RSS0.9 feeds.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ * @todo Find a Relax NG URI we can use
+ */
+class XML_Feed_Parser_RSS09 extends XML_Feed_Parser_Type
+{
+ /**
+ * The URI of the RelaxNG schema used to (optionally) validate the feed
+ * @var string
+ */
+ private $relax = '';
+
+ /**
+ * We're likely to use XPath, so let's keep it global
+ * @var DOMXPath
+ */
+ protected $xpath;
+
+ /**
+ * The feed type we are parsing
+ * @var string
+ */
+ public $version = 'RSS 0.9';
+
+ /**
+ * The class used to represent individual items
+ * @var string
+ */
+ protected $itemClass = 'XML_Feed_Parser_RSS09Element';
+
+ /**
+ * The element containing entries
+ * @var string
+ */
+ protected $itemElement = 'item';
+
+ /**
+ * Here we map those elements we're not going to handle individually
+ * to the constructs they are. The optional second parameter in the array
+ * tells the parser whether to 'fall back' (not apt. at the feed level) or
+ * fail if the element is missing. If the parameter is not set, the function
+ * will simply return false and leave it to the client to decide what to do.
+ * @var array
+ */
+ protected $map = array(
+ 'title' => array('Text'),
+ 'link' => array('Text'),
+ 'description' => array('Text'),
+ 'image' => array('Image'),
+ 'textinput' => array('TextInput'));
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS2.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'title' => array('title'),
+ 'link' => array('link'),
+ 'subtitle' => array('description'));
+
+ /**
+ * We will be working with multiple namespaces and it is useful to
+ * keep them together
+ * @var array
+ */
+ protected $namespaces = array(
+ 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
+
+ /**
+ * Our constructor does nothing more than its parent.
+ *
+ * @todo RelaxNG validation
+ * @param DOMDocument $xml A DOM object representing the feed
+ * @param bool (optional) $string Whether or not to validate this feed
+ */
+ function __construct(DOMDocument $model, $strict = false)
+ {
+ $this->model = $model;
+
+ $this->xpath = new DOMXPath($model);
+ foreach ($this->namespaces as $key => $value) {
+ $this->xpath->registerNamespace($key, $value);
+ }
+ $this->numberEntries = $this->count('item');
+ }
+
+ /**
+ * Included for compatibility -- will not work with RSS 0.9
+ *
+ * This is not something that will work with RSS0.9 as it does not have
+ * clear restrictions on the global uniqueness of IDs.
+ *
+ * @param string $id any valid ID.
+ * @return false
+ */
+ function getEntryById($id)
+ {
+ return false;
+ }
+
+ /**
+ * Get details of the image associated with the feed.
+ *
+ * @return array|false an array simply containing the child elements
+ */
+ protected function getImage()
+ {
+ $images = $this->model->getElementsByTagName('image');
+ if ($images->length > 0) {
+ $image = $images->item(0);
+ $details = array();
+ if ($image->hasChildNodes()) {
+ $details = array(
+ 'title' => $image->getElementsByTagName('title')->item(0)->value,
+ 'link' => $image->getElementsByTagName('link')->item(0)->value,
+ 'url' => $image->getElementsByTagName('url')->item(0)->value);
+ } else {
+ $details = array('title' => false,
+ 'link' => false,
+ 'url' => $image->attributes->getNamedItem('resource')->nodeValue);
+ }
+ $details = array_merge($details,
+ array('description' => false, 'height' => false, 'width' => false));
+ if (! empty($details)) {
+ return $details;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * The textinput element is little used, but in the interests of
+ * completeness we will support it.
+ *
+ * @return array|false
+ */
+ protected function getTextInput()
+ {
+ $inputs = $this->model->getElementsByTagName('textinput');
+ if ($inputs->length > 0) {
+ $input = $inputs->item(0);
+ $results = array();
+ $results['title'] = isset(
+ $input->getElementsByTagName('title')->item(0)->value) ?
+ $input->getElementsByTagName('title')->item(0)->value : null;
+ $results['description'] = isset(
+ $input->getElementsByTagName('description')->item(0)->value) ?
+ $input->getElementsByTagName('description')->item(0)->value : null;
+ $results['name'] = isset(
+ $input->getElementsByTagName('name')->item(0)->value) ?
+ $input->getElementsByTagName('name')->item(0)->value : null;
+ $results['link'] = isset(
+ $input->getElementsByTagName('link')->item(0)->value) ?
+ $input->getElementsByTagName('link')->item(0)->value : null;
+ if (empty($results['link']) &&
+ $input->attributes->getNamedItem('resource')) {
+ $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue;
+ }
+ if (! empty($results)) {
+ return $results;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get details of a link from the feed.
+ *
+ * In RSS1 a link is a text element but in order to ensure that we resolve
+ * URLs properly we have a special function for them.
+ *
+ * @return string
+ */
+ function getLink($offset = 0, $attribute = 'href', $params = false)
+ {
+ $links = $this->model->getElementsByTagName('link');
+ if ($links->length <= $offset) {
+ return false;
+ }
+ $link = $links->item($offset);
+ return $this->addBase($link->nodeValue, $link);
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * RSS0.9 Element class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS09Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/*
+ * This class provides support for RSS 0.9 entries. It will usually be called by
+ * XML_Feed_Parser_RSS09 with which it shares many methods.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_RSS09Element extends XML_Feed_Parser_RSS09
+{
+ /**
+ * This will be a reference to the parent object for when we want
+ * to use a 'fallback' rule
+ * @var XML_Feed_Parser_RSS09
+ */
+ protected $parent;
+
+ /**
+ * Our specific element map
+ * @var array
+ */
+ protected $map = array(
+ 'title' => array('Text'),
+ 'link' => array('Link'));
+
+ /**
+ * Store useful information for later.
+ *
+ * @param DOMElement $element - this item as a DOM element
+ * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member
+ */
+ function __construct(DOMElement $element, $parent, $xmlBase = '')
+ {
+ $this->model = $element;
+ $this->parent = $parent;
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * RSS1 class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS1.php,v 1.10 2006/07/27 13:52:05 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This class handles RSS1.0 feeds.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ * @todo Find a Relax NG URI we can use
+ */
+class XML_Feed_Parser_RSS1 extends XML_Feed_Parser_Type
+{
+ /**
+ * The URI of the RelaxNG schema used to (optionally) validate the feed
+ * @var string
+ */
+ private $relax = 'rss10.rnc';
+
+ /**
+ * We're likely to use XPath, so let's keep it global
+ * @var DOMXPath
+ */
+ protected $xpath;
+
+ /**
+ * The feed type we are parsing
+ * @var string
+ */
+ public $version = 'RSS 1.0';
+
+ /**
+ * The class used to represent individual items
+ * @var string
+ */
+ protected $itemClass = 'XML_Feed_Parser_RSS1Element';
+
+ /**
+ * The element containing entries
+ * @var string
+ */
+ protected $itemElement = 'item';
+
+ /**
+ * Here we map those elements we're not going to handle individually
+ * to the constructs they are. The optional second parameter in the array
+ * tells the parser whether to 'fall back' (not apt. at the feed level) or
+ * fail if the element is missing. If the parameter is not set, the function
+ * will simply return false and leave it to the client to decide what to do.
+ * @var array
+ */
+ protected $map = array(
+ 'title' => array('Text'),
+ 'link' => array('Text'),
+ 'description' => array('Text'),
+ 'image' => array('Image'),
+ 'textinput' => array('TextInput'),
+ 'updatePeriod' => array('Text'),
+ 'updateFrequency' => array('Text'),
+ 'updateBase' => array('Date'),
+ 'rights' => array('Text'), # dc:rights
+ 'description' => array('Text'), # dc:description
+ 'creator' => array('Text'), # dc:creator
+ 'publisher' => array('Text'), # dc:publisher
+ 'contributor' => array('Text'), # dc:contributor
+ 'date' => array('Date') # dc:contributor
+ );
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS2.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'title' => array('title'),
+ 'link' => array('link'),
+ 'subtitle' => array('description'),
+ 'author' => array('creator'),
+ 'updated' => array('date'));
+
+ /**
+ * We will be working with multiple namespaces and it is useful to
+ * keep them together
+ * @var array
+ */
+ protected $namespaces = array(
+ 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'rss' => 'http://purl.org/rss/1.0/',
+ 'dc' => 'http://purl.org/rss/1.0/modules/dc/',
+ 'content' => 'http://purl.org/rss/1.0/modules/content/',
+ 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/');
+
+ /**
+ * Our constructor does nothing more than its parent.
+ *
+ * @param DOMDocument $xml A DOM object representing the feed
+ * @param bool (optional) $string Whether or not to validate this feed
+ */
+ function __construct(DOMDocument $model, $strict = false)
+ {
+ $this->model = $model;
+ if ($strict) {
+ $validate = $this->model->relaxNGValidate(self::getSchemaDir .
+ DIRECTORY_SEPARATOR . $this->relax);
+ if (! $validate) {
+ throw new XML_Feed_Parser_Exception('Failed required validation');
+ }
+ }
+
+ $this->xpath = new DOMXPath($model);
+ foreach ($this->namespaces as $key => $value) {
+ $this->xpath->registerNamespace($key, $value);
+ }
+ $this->numberEntries = $this->count('item');
+ }
+
+ /**
+ * Allows retrieval of an entry by ID where the rdf:about attribute is used
+ *
+ * This is not really something that will work with RSS1 as it does not have
+ * clear restrictions on the global uniqueness of IDs. We will employ the
+ * _very_ hit and miss method of selecting entries based on the rdf:about
+ * attribute. If DOMXPath::evaluate is available, we also use that to store
+ * a reference to the entry in the array used by getEntryByOffset so that
+ * method does not have to seek out the entry if it's requested that way.
+ *
+ * @param string $id any valid ID.
+ * @return XML_Feed_Parser_RSS1Element
+ */
+ function getEntryById($id)
+ {
+ if (isset($this->idMappings[$id])) {
+ return $this->entries[$this->idMappings[$id]];
+ }
+
+ $entries = $this->xpath->query("//rss:item[@rdf:about='$id']");
+ if ($entries->length > 0) {
+ $classname = $this->itemClass;
+ $entry = new $classname($entries->item(0), $this);
+ if (in_array('evaluate', get_class_methods($this->xpath))) {
+ $offset = $this->xpath->evaluate("count(preceding-sibling::rss:item)", $entries->item(0));
+ $this->entries[$offset] = $entry;
+ }
+ $this->idMappings[$id] = $entry;
+ return $entry;
+ }
+ return false;
+ }
+
+ /**
+ * Get details of the image associated with the feed.
+ *
+ * @return array|false an array simply containing the child elements
+ */
+ protected function getImage()
+ {
+ $images = $this->model->getElementsByTagName('image');
+ if ($images->length > 0) {
+ $image = $images->item(0);
+ $details = array();
+ if ($image->hasChildNodes()) {
+ $details = array(
+ 'title' => $image->getElementsByTagName('title')->item(0)->value,
+ 'link' => $image->getElementsByTagName('link')->item(0)->value,
+ 'url' => $image->getElementsByTagName('url')->item(0)->value);
+ } else {
+ $details = array('title' => false,
+ 'link' => false,
+ 'url' => $image->attributes->getNamedItem('resource')->nodeValue);
+ }
+ $details = array_merge($details, array('description' => false, 'height' => false, 'width' => false));
+ if (! empty($details)) {
+ return $details;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * The textinput element is little used, but in the interests of
+ * completeness we will support it.
+ *
+ * @return array|false
+ */
+ protected function getTextInput()
+ {
+ $inputs = $this->model->getElementsByTagName('textinput');
+ if ($inputs->length > 0) {
+ $input = $inputs->item(0);
+ $results = array();
+ $results['title'] = isset(
+ $input->getElementsByTagName('title')->item(0)->value) ?
+ $input->getElementsByTagName('title')->item(0)->value : null;
+ $results['description'] = isset(
+ $input->getElementsByTagName('description')->item(0)->value) ?
+ $input->getElementsByTagName('description')->item(0)->value : null;
+ $results['name'] = isset(
+ $input->getElementsByTagName('name')->item(0)->value) ?
+ $input->getElementsByTagName('name')->item(0)->value : null;
+ $results['link'] = isset(
+ $input->getElementsByTagName('link')->item(0)->value) ?
+ $input->getElementsByTagName('link')->item(0)->value : null;
+ if (empty($results['link']) and
+ $input->attributes->getNamedItem('resource')) {
+ $results['link'] =
+ $input->attributes->getNamedItem('resource')->nodeValue;
+ }
+ if (! empty($results)) {
+ return $results;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Employs various techniques to identify the author
+ *
+ * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher
+ * elements for defining authorship in RSS1. We will try each of those in
+ * turn in order to simulate the atom author element and will return it
+ * as text.
+ *
+ * @return array|false
+ */
+ function getAuthor()
+ {
+ $options = array('creator', 'contributor', 'publisher');
+ foreach ($options as $element) {
+ $test = $this->model->getElementsByTagName($element);
+ if ($test->length > 0) {
+ return $test->item(0)->value;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve a link
+ *
+ * In RSS1 a link is a text element but in order to ensure that we resolve
+ * URLs properly we have a special function for them.
+ *
+ * @return string
+ */
+ function getLink($offset = 0, $attribute = 'href', $params = false)
+ {
+ $links = $this->model->getElementsByTagName('link');
+ if ($links->length <= $offset) {
+ return false;
+ }
+ $link = $links->item($offset);
+ return $this->addBase($link->nodeValue, $link);
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * RSS1.1 class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS11.php,v 1.6 2006/07/27 13:52:05 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This class handles RSS1.1 feeds. RSS1.1 is documented at:
+ * http://inamidst.com/rss1.1/
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ * @todo Support for RDF:List
+ * @todo Ensure xml:lang is accessible to users
+ */
+class XML_Feed_Parser_RSS11 extends XML_Feed_Parser_Type
+{
+ /**
+ * The URI of the RelaxNG schema used to (optionally) validate the feed
+ * @var string
+ */
+ private $relax = 'rss11.rnc';
+
+ /**
+ * We're likely to use XPath, so let's keep it global
+ * @var DOMXPath
+ */
+ protected $xpath;
+
+ /**
+ * The feed type we are parsing
+ * @var string
+ */
+ public $version = 'RSS 1.0';
+
+ /**
+ * The class used to represent individual items
+ * @var string
+ */
+ protected $itemClass = 'XML_Feed_Parser_RSS1Element';
+
+ /**
+ * The element containing entries
+ * @var string
+ */
+ protected $itemElement = 'item';
+
+ /**
+ * Here we map those elements we're not going to handle individually
+ * to the constructs they are. The optional second parameter in the array
+ * tells the parser whether to 'fall back' (not apt. at the feed level) or
+ * fail if the element is missing. If the parameter is not set, the function
+ * will simply return false and leave it to the client to decide what to do.
+ * @var array
+ */
+ protected $map = array(
+ 'title' => array('Text'),
+ 'link' => array('Text'),
+ 'description' => array('Text'),
+ 'image' => array('Image'),
+ 'updatePeriod' => array('Text'),
+ 'updateFrequency' => array('Text'),
+ 'updateBase' => array('Date'),
+ 'rights' => array('Text'), # dc:rights
+ 'description' => array('Text'), # dc:description
+ 'creator' => array('Text'), # dc:creator
+ 'publisher' => array('Text'), # dc:publisher
+ 'contributor' => array('Text'), # dc:contributor
+ 'date' => array('Date') # dc:contributor
+ );
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS2.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'title' => array('title'),
+ 'link' => array('link'),
+ 'subtitle' => array('description'),
+ 'author' => array('creator'),
+ 'updated' => array('date'));
+
+ /**
+ * We will be working with multiple namespaces and it is useful to
+ * keep them together. We will retain support for some common RSS1.0 modules
+ * @var array
+ */
+ protected $namespaces = array(
+ 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'rss' => 'http://purl.org/net/rss1.1#',
+ 'dc' => 'http://purl.org/rss/1.0/modules/dc/',
+ 'content' => 'http://purl.org/rss/1.0/modules/content/',
+ 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/');
+
+ /**
+ * Our constructor does nothing more than its parent.
+ *
+ * @param DOMDocument $xml A DOM object representing the feed
+ * @param bool (optional) $string Whether or not to validate this feed
+ */
+ function __construct(DOMDocument $model, $strict = false)
+ {
+ $this->model = $model;
+
+ if ($strict) {
+ $validate = $this->model->relaxNGValidate(self::getSchemaDir .
+ DIRECTORY_SEPARATOR . $this->relax);
+ if (! $validate) {
+ throw new XML_Feed_Parser_Exception('Failed required validation');
+ }
+ }
+
+ $this->xpath = new DOMXPath($model);
+ foreach ($this->namespaces as $key => $value) {
+ $this->xpath->registerNamespace($key, $value);
+ }
+ $this->numberEntries = $this->count('item');
+ }
+
+ /**
+ * Attempts to identify an element by ID given by the rdf:about attribute
+ *
+ * This is not really something that will work with RSS1.1 as it does not have
+ * clear restrictions on the global uniqueness of IDs. We will employ the
+ * _very_ hit and miss method of selecting entries based on the rdf:about
+ * attribute. Please note that this is even more hit and miss with RSS1.1 than
+ * with RSS1.0 since RSS1.1 does not require the rdf:about attribute for items.
+ *
+ * @param string $id any valid ID.
+ * @return XML_Feed_Parser_RSS1Element
+ */
+ function getEntryById($id)
+ {
+ if (isset($this->idMappings[$id])) {
+ return $this->entries[$this->idMappings[$id]];
+ }
+
+ $entries = $this->xpath->query("//rss:item[@rdf:about='$id']");
+ if ($entries->length > 0) {
+ $classname = $this->itemClass;
+ $entry = new $classname($entries->item(0), $this);
+ return $entry;
+ }
+ return false;
+ }
+
+ /**
+ * Get details of the image associated with the feed.
+ *
+ * @return array|false an array simply containing the child elements
+ */
+ protected function getImage()
+ {
+ $images = $this->model->getElementsByTagName('image');
+ if ($images->length > 0) {
+ $image = $images->item(0);
+ $details = array();
+ if ($image->hasChildNodes()) {
+ $details = array(
+ 'title' => $image->getElementsByTagName('title')->item(0)->value,
+ 'url' => $image->getElementsByTagName('url')->item(0)->value);
+ if ($image->getElementsByTagName('link')->length > 0) {
+ $details['link'] =
+ $image->getElementsByTagName('link')->item(0)->value;
+ }
+ } else {
+ $details = array('title' => false,
+ 'link' => false,
+ 'url' => $image->attributes->getNamedItem('resource')->nodeValue);
+ }
+ $details = array_merge($details,
+ array('description' => false, 'height' => false, 'width' => false));
+ if (! empty($details)) {
+ return $details;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * The textinput element is little used, but in the interests of
+ * completeness we will support it.
+ *
+ * @return array|false
+ */
+ protected function getTextInput()
+ {
+ $inputs = $this->model->getElementsByTagName('textinput');
+ if ($inputs->length > 0) {
+ $input = $inputs->item(0);
+ $results = array();
+ $results['title'] = isset(
+ $input->getElementsByTagName('title')->item(0)->value) ?
+ $input->getElementsByTagName('title')->item(0)->value : null;
+ $results['description'] = isset(
+ $input->getElementsByTagName('description')->item(0)->value) ?
+ $input->getElementsByTagName('description')->item(0)->value : null;
+ $results['name'] = isset(
+ $input->getElementsByTagName('name')->item(0)->value) ?
+ $input->getElementsByTagName('name')->item(0)->value : null;
+ $results['link'] = isset(
+ $input->getElementsByTagName('link')->item(0)->value) ?
+ $input->getElementsByTagName('link')->item(0)->value : null;
+ if (empty($results['link']) and
+ $input->attributes->getNamedItem('resource')) {
+ $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue;
+ }
+ if (! empty($results)) {
+ return $results;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to discern authorship
+ *
+ * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher
+ * elements for defining authorship in RSS1. We will try each of those in
+ * turn in order to simulate the atom author element and will return it
+ * as text.
+ *
+ * @return array|false
+ */
+ function getAuthor()
+ {
+ $options = array('creator', 'contributor', 'publisher');
+ foreach ($options as $element) {
+ $test = $this->model->getElementsByTagName($element);
+ if ($test->length > 0) {
+ return $test->item(0)->value;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve a link
+ *
+ * In RSS1 a link is a text element but in order to ensure that we resolve
+ * URLs properly we have a special function for them.
+ *
+ * @return string
+ */
+ function getLink($offset = 0, $attribute = 'href', $params = false)
+ {
+ $links = $this->model->getElementsByTagName('link');
+ if ($links->length <= $offset) {
+ return false;
+ }
+ $link = $links->item($offset);
+ return $this->addBase($link->nodeValue, $link);
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * RSS1 Element class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS11Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/*
+ * This class provides support for RSS 1.1 entries. It will usually be called by
+ * XML_Feed_Parser_RSS11 with which it shares many methods.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_RSS11Element extends XML_Feed_Parser_RSS11
+{
+ /**
+ * This will be a reference to the parent object for when we want
+ * to use a 'fallback' rule
+ * @var XML_Feed_Parser_RSS1
+ */
+ protected $parent;
+
+ /**
+ * Our specific element map
+ * @var array
+ */
+ protected $map = array(
+ 'id' => array('Id'),
+ 'title' => array('Text'),
+ 'link' => array('Link'),
+ 'description' => array('Text'), # or dc:description
+ 'category' => array('Category'),
+ 'rights' => array('Text'), # dc:rights
+ 'creator' => array('Text'), # dc:creator
+ 'publisher' => array('Text'), # dc:publisher
+ 'contributor' => array('Text'), # dc:contributor
+ 'date' => array('Date'), # dc:date
+ 'content' => array('Content')
+ );
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS1.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'content' => array('content'),
+ 'updated' => array('lastBuildDate'),
+ 'published' => array('pubdate'),
+ 'subtitle' => array('description'),
+ 'updated' => array('date'),
+ 'author' => array('creator'),
+ 'contributor' => array('contributor')
+ );
+
+ /**
+ * Store useful information for later.
+ *
+ * @param DOMElement $element - this item as a DOM element
+ * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member
+ */
+ function __construct(DOMElement $element, $parent, $xmlBase = '')
+ {
+ $this->model = $element;
+ $this->parent = $parent;
+ }
+
+ /**
+ * If an rdf:about attribute is specified, return that as an ID
+ *
+ * There is no established way of showing an ID for an RSS1 entry. We will
+ * simulate it using the rdf:about attribute of the entry element. This cannot
+ * be relied upon for unique IDs but may prove useful.
+ *
+ * @return string|false
+ */
+ function getId()
+ {
+ if ($this->model->attributes->getNamedItem('about')) {
+ return $this->model->attributes->getNamedItem('about')->nodeValue;
+ }
+ return false;
+ }
+
+ /**
+ * Return the entry's content
+ *
+ * The official way to include full content in an RSS1 entry is to use
+ * the content module's element 'encoded'. Often, however, the 'description'
+ * element is used instead. We will offer that as a fallback.
+ *
+ * @return string|false
+ */
+ function getContent()
+ {
+ $options = array('encoded', 'description');
+ foreach ($options as $element) {
+ $test = $this->model->getElementsByTagName($element);
+ if ($test->length == 0) {
+ continue;
+ }
+ if ($test->item(0)->hasChildNodes()) {
+ $value = '';
+ foreach ($test->item(0)->childNodes as $child) {
+ if ($child instanceof DOMText) {
+ $value .= $child->nodeValue;
+ } else {
+ $simple = simplexml_import_dom($child);
+ $value .= $simple->asXML();
+ }
+ }
+ return $value;
+ } else if ($test->length > 0) {
+ return $test->item(0)->nodeValue;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * How RSS1.1 should support for enclosures is not clear. For now we will return
+ * false.
+ *
+ * @return false
+ */
+ function getEnclosure()
+ {
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * RSS1 Element class for XML_Feed_Parser
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS1Element.php,v 1.6 2006/06/30 17:41:56 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/*
+ * This class provides support for RSS 1.0 entries. It will usually be called by
+ * XML_Feed_Parser_RSS1 with which it shares many methods.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_RSS1Element extends XML_Feed_Parser_RSS1
+{
+ /**
+ * This will be a reference to the parent object for when we want
+ * to use a 'fallback' rule
+ * @var XML_Feed_Parser_RSS1
+ */
+ protected $parent;
+
+ /**
+ * Our specific element map
+ * @var array
+ */
+ protected $map = array(
+ 'id' => array('Id'),
+ 'title' => array('Text'),
+ 'link' => array('Link'),
+ 'description' => array('Text'), # or dc:description
+ 'category' => array('Category'),
+ 'rights' => array('Text'), # dc:rights
+ 'creator' => array('Text'), # dc:creator
+ 'publisher' => array('Text'), # dc:publisher
+ 'contributor' => array('Text'), # dc:contributor
+ 'date' => array('Date'), # dc:date
+ 'content' => array('Content')
+ );
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS1.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'content' => array('content'),
+ 'updated' => array('lastBuildDate'),
+ 'published' => array('pubdate'),
+ 'subtitle' => array('description'),
+ 'updated' => array('date'),
+ 'author' => array('creator'),
+ 'contributor' => array('contributor')
+ );
+
+ /**
+ * Store useful information for later.
+ *
+ * @param DOMElement $element - this item as a DOM element
+ * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member
+ */
+ function __construct(DOMElement $element, $parent, $xmlBase = '')
+ {
+ $this->model = $element;
+ $this->parent = $parent;
+ }
+
+ /**
+ * If an rdf:about attribute is specified, return it as an ID
+ *
+ * There is no established way of showing an ID for an RSS1 entry. We will
+ * simulate it using the rdf:about attribute of the entry element. This cannot
+ * be relied upon for unique IDs but may prove useful.
+ *
+ * @return string|false
+ */
+ function getId()
+ {
+ if ($this->model->attributes->getNamedItem('about')) {
+ return $this->model->attributes->getNamedItem('about')->nodeValue;
+ }
+ return false;
+ }
+
+ /**
+ * How RSS1 should support for enclosures is not clear. For now we will return
+ * false.
+ *
+ * @return false
+ */
+ function getEnclosure()
+ {
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Class representing feed-level data for an RSS2 feed
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS2.php,v 1.11 2006/07/27 13:52:05 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This class handles RSS2 feeds.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type
+{
+ /**
+ * The URI of the RelaxNG schema used to (optionally) validate the feed
+ * @var string
+ */
+ private $relax = 'rss20.rnc';
+
+ /**
+ * We're likely to use XPath, so let's keep it global
+ * @var DOMXPath
+ */
+ protected $xpath;
+
+ /**
+ * The feed type we are parsing
+ * @var string
+ */
+ public $version = 'RSS 2.0';
+
+ /**
+ * The class used to represent individual items
+ * @var string
+ */
+ protected $itemClass = 'XML_Feed_Parser_RSS2Element';
+
+ /**
+ * The element containing entries
+ * @var string
+ */
+ protected $itemElement = 'item';
+
+ /**
+ * Here we map those elements we're not going to handle individually
+ * to the constructs they are. The optional second parameter in the array
+ * tells the parser whether to 'fall back' (not apt. at the feed level) or
+ * fail if the element is missing. If the parameter is not set, the function
+ * will simply return false and leave it to the client to decide what to do.
+ * @var array
+ */
+ protected $map = array(
+ 'ttl' => array('Text'),
+ 'pubDate' => array('Date'),
+ 'lastBuildDate' => array('Date'),
+ 'title' => array('Text'),
+ 'link' => array('Link'),
+ 'description' => array('Text'),
+ 'language' => array('Text'),
+ 'copyright' => array('Text'),
+ 'managingEditor' => array('Text'),
+ 'webMaster' => array('Text'),
+ 'category' => array('Text'),
+ 'generator' => array('Text'),
+ 'docs' => array('Text'),
+ 'ttl' => array('Text'),
+ 'image' => array('Image'),
+ 'skipDays' => array('skipDays'),
+ 'skipHours' => array('skipHours'));
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS2.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'title' => array('title'),
+ 'rights' => array('copyright'),
+ 'updated' => array('lastBuildDate'),
+ 'subtitle' => array('description'),
+ 'date' => array('pubDate'),
+ 'author' => array('managingEditor'));
+
+ protected $namespaces = array(
+ 'dc' => 'http://purl.org/rss/1.0/modules/dc/',
+ 'content' => 'http://purl.org/rss/1.0/modules/content/');
+
+ /**
+ * Our constructor does nothing more than its parent.
+ *
+ * @param DOMDocument $xml A DOM object representing the feed
+ * @param bool (optional) $string Whether or not to validate this feed
+ */
+ function __construct(DOMDocument $model, $strict = false)
+ {
+ $this->model = $model;
+
+ if ($strict) {
+ if (! $this->model->relaxNGValidate($this->relax)) {
+ throw new XML_Feed_Parser_Exception('Failed required validation');
+ }
+ }
+
+ $this->xpath = new DOMXPath($this->model);
+ foreach ($this->namespaces as $key => $value) {
+ $this->xpath->registerNamespace($key, $value);
+ }
+ $this->numberEntries = $this->count('item');
+ }
+
+ /**
+ * Retrieves an entry by ID, if the ID is specified with the guid element
+ *
+ * This is not really something that will work with RSS2 as it does not have
+ * clear restrictions on the global uniqueness of IDs. But we can emulate
+ * it by allowing access based on the 'guid' element. If DOMXPath::evaluate
+ * is available, we also use that to store a reference to the entry in the array
+ * used by getEntryByOffset so that method does not have to seek out the entry
+ * if it's requested that way.
+ *
+ * @param string $id any valid ID.
+ * @return XML_Feed_Parser_RSS2Element
+ */
+ function getEntryById($id)
+ {
+ if (isset($this->idMappings[$id])) {
+ return $this->entries[$this->idMappings[$id]];
+ }
+
+ $entries = $this->xpath->query("//item[guid='$id']");
+ if ($entries->length > 0) {
+ $entry = new $this->itemElement($entries->item(0), $this);
+ if (in_array('evaluate', get_class_methods($this->xpath))) {
+ $offset = $this->xpath->evaluate("count(preceding-sibling::item)", $entries->item(0));
+ $this->entries[$offset] = $entry;
+ }
+ $this->idMappings[$id] = $entry;
+ return $entry;
+ }
+ }
+
+ /**
+ * Get a category from the element
+ *
+ * The category element is a simple text construct which can occur any number
+ * of times. We allow access by offset or access to an array of results.
+ *
+ * @param string $call for compatibility with our overloading
+ * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array
+ * @return string|array|false
+ */
+ function getCategory($call, $arguments = array())
+ {
+ $categories = $this->model->getElementsByTagName('category');
+ $offset = empty($arguments[0]) ? 0 : $arguments[0];
+ $array = empty($arguments[1]) ? false : true;
+ if ($categories->length <= $offset) {
+ return false;
+ }
+ if ($array) {
+ $list = array();
+ foreach ($categories as $category) {
+ array_push($list, $category->nodeValue);
+ }
+ return $list;
+ }
+ return $categories->item($offset)->nodeValue;
+ }
+
+ /**
+ * Get details of the image associated with the feed.
+ *
+ * @return array|false an array simply containing the child elements
+ */
+ protected function getImage()
+ {
+ $images = $this->model->getElementsByTagName('image');
+ if ($images->length > 0) {
+ $image = $images->item(0);
+ $desc = $image->getElementsByTagName('description');
+ $description = $desc->length ? $desc->item(0)->nodeValue : false;
+ $heigh = $image->getElementsByTagName('height');
+ $height = $heigh->length ? $heigh->item(0)->nodeValue : false;
+ $widt = $image->getElementsByTagName('width');
+ $width = $widt->length ? $widt->item(0)->nodeValue : false;
+ return array(
+ 'title' => $image->getElementsByTagName('title')->item(0)->nodeValue,
+ 'link' => $image->getElementsByTagName('link')->item(0)->nodeValue,
+ 'url' => $image->getElementsByTagName('url')->item(0)->nodeValue,
+ 'description' => $description,
+ 'height' => $height,
+ 'width' => $width);
+ }
+ return false;
+ }
+
+ /**
+ * The textinput element is little used, but in the interests of
+ * completeness...
+ *
+ * @return array|false
+ */
+ function getTextInput()
+ {
+ $inputs = $this->model->getElementsByTagName('input');
+ if ($inputs->length > 0) {
+ $input = $inputs->item(0);
+ return array(
+ 'title' => $input->getElementsByTagName('title')->item(0)->value,
+ 'description' =>
+ $input->getElementsByTagName('description')->item(0)->value,
+ 'name' => $input->getElementsByTagName('name')->item(0)->value,
+ 'link' => $input->getElementsByTagName('link')->item(0)->value);
+ }
+ return false;
+ }
+
+ /**
+ * Utility function for getSkipDays and getSkipHours
+ *
+ * This is a general function used by both getSkipDays and getSkipHours. It simply
+ * returns an array of the values of the children of the appropriate tag.
+ *
+ * @param string $tagName The tag name (getSkipDays or getSkipHours)
+ * @return array|false
+ */
+ protected function getSkips($tagName)
+ {
+ $hours = $this->model->getElementsByTagName($tagName);
+ if ($hours->length == 0) {
+ return false;
+ }
+ $skipHours = array();
+ foreach($hours->item(0)->childNodes as $hour) {
+ if ($hour instanceof DOMElement) {
+ array_push($skipHours, $hour->nodeValue);
+ }
+ }
+ return $skipHours;
+ }
+
+ /**
+ * Retrieve skipHours data
+ *
+ * The skiphours element provides a list of hours on which this feed should
+ * not be checked. We return an array of those hours (integers, 24 hour clock)
+ *
+ * @return array
+ */
+ function getSkipHours()
+ {
+ return $this->getSkips('skipHours');
+ }
+
+ /**
+ * Retrieve skipDays data
+ *
+ * The skipdays element provides a list of days on which this feed should
+ * not be checked. We return an array of those days.
+ *
+ * @return array
+ */
+ function getSkipDays()
+ {
+ return $this->getSkips('skipDays');
+ }
+
+ /**
+ * Return content of the little-used 'cloud' element
+ *
+ * The cloud element is rarely used. It is designed to provide some details
+ * of a location to update the feed.
+ *
+ * @return array an array of the attributes of the element
+ */
+ function getCloud()
+ {
+ $cloud = $this->model->getElementsByTagName('cloud');
+ if ($cloud->length == 0) {
+ return false;
+ }
+ $cloudData = array();
+ foreach ($cloud->item(0)->attributes as $attribute) {
+ $cloudData[$attribute->name] = $attribute->value;
+ }
+ return $cloudData;
+ }
+
+ /**
+ * Get link URL
+ *
+ * In RSS2 a link is a text element but in order to ensure that we resolve
+ * URLs properly we have a special function for them. We maintain the
+ * parameter used by the atom getLink method, though we only use the offset
+ * parameter.
+ *
+ * @param int $offset The position of the link within the feed. Starts from 0
+ * @param string $attribute The attribute of the link element required
+ * @param array $params An array of other parameters. Not used.
+ * @return string
+ */
+ function getLink($offset, $attribute = 'href', $params = array())
+ {
+ $links = $this->model->getElementsByTagName('link');
+
+ if ($links->length <= $offset) {
+ return false;
+ }
+ $link = $links->item($offset);
+ return $this->addBase($link->nodeValue, $link);
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Class representing entries in an RSS2 feed.
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: RSS2Element.php,v 1.11 2006/07/26 21:18:47 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This class provides support for RSS 2.0 entries. It will usually be
+ * called by XML_Feed_Parser_RSS2 with which it shares many methods.
+ *
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ * @package XML_Feed_Parser
+ */
+class XML_Feed_Parser_RSS2Element extends XML_Feed_Parser_RSS2
+{
+ /**
+ * This will be a reference to the parent object for when we want
+ * to use a 'fallback' rule
+ * @var XML_Feed_Parser_RSS2
+ */
+ protected $parent;
+
+ /**
+ * Our specific element map
+ * @var array
+ */
+ protected $map = array(
+ 'title' => array('Text'),
+ 'guid' => array('Guid'),
+ 'description' => array('Text'),
+ 'author' => array('Text'),
+ 'comments' => array('Text'),
+ 'enclosure' => array('Enclosure'),
+ 'pubDate' => array('Date'),
+ 'source' => array('Source'),
+ 'link' => array('Text'),
+ 'content' => array('Content'));
+
+ /**
+ * Here we map some elements to their atom equivalents. This is going to be
+ * quite tricky to pull off effectively (and some users' methods may vary)
+ * but is worth trying. The key is the atom version, the value is RSS2.
+ * @var array
+ */
+ protected $compatMap = array(
+ 'id' => array('guid'),
+ 'updated' => array('lastBuildDate'),
+ 'published' => array('pubdate'),
+ 'guidislink' => array('guid', 'ispermalink'),
+ 'summary' => array('description'));
+
+ /**
+ * Store useful information for later.
+ *
+ * @param DOMElement $element - this item as a DOM element
+ * @param XML_Feed_Parser_RSS2 $parent - the feed of which this is a member
+ */
+ function __construct(DOMElement $element, $parent, $xmlBase = '')
+ {
+ $this->model = $element;
+ $this->parent = $parent;
+ }
+
+ /**
+ * Get the value of the guid element, if specified
+ *
+ * guid is the closest RSS2 has to atom's ID. It is usually but not always a
+ * URI. The one attribute that RSS2 can posess is 'ispermalink' which specifies
+ * whether the guid is itself dereferencable. Use of guid is not obligatory,
+ * but is advisable. To get the guid you would call $item->id() (for atom
+ * compatibility) or $item->guid(). To check if this guid is a permalink call
+ * $item->guid("ispermalink").
+ *
+ * @param string $method - the method name being called
+ * @param array $params - parameters required
+ * @return string the guid or value of ispermalink
+ */
+ protected function getGuid($method, $params)
+ {
+ $attribute = (isset($params[0]) and $params[0] == 'ispermalink') ?
+ true : false;
+ $tag = $this->model->getElementsByTagName('guid');
+ if ($tag->length > 0) {
+ if ($attribute) {
+ if ($tag->hasAttribute("ispermalink")) {
+ return $tag->getAttribute("ispermalink");
+ }
+ }
+ return $tag->item(0)->nodeValue;
+ }
+ return false;
+ }
+
+ /**
+ * Access details of file enclosures
+ *
+ * The RSS2 spec is ambiguous as to whether an enclosure element must be
+ * unique in a given entry. For now we will assume it needn't, and allow
+ * for an offset.
+ *
+ * @param string $method - the method being called
+ * @param array $parameters - we expect the first of these to be our offset
+ * @return array|false
+ */
+ protected function getEnclosure($method, $parameters)
+ {
+ $encs = $this->model->getElementsByTagName('enclosure');
+ $offset = isset($parameters[0]) ? $parameters[0] : 0;
+ if ($encs->length > $offset) {
+ try {
+ if (! $encs->item($offset)->hasAttribute('url')) {
+ return false;
+ }
+ $attrs = $encs->item($offset)->attributes;
+ return array(
+ 'url' => $attrs->getNamedItem('url')->value,
+ 'length' => $attrs->getNamedItem('length')->value,
+ 'type' => $attrs->getNamedItem('type')->value);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the entry source if specified
+ *
+ * source is an optional sub-element of item. Like atom:source it tells
+ * us about where the entry came from (eg. if it's been copied from another
+ * feed). It is not a rich source of metadata in the same way as atom:source
+ * and while it would be good to maintain compatibility by returning an
+ * XML_Feed_Parser_RSS2 element, it makes a lot more sense to return an array.
+ *
+ * @return array|false
+ */
+ protected function getSource()
+ {
+ $get = $this->model->getElementsByTagName('source');
+ if ($get->length) {
+ $source = $get->item(0);
+ $array = array(
+ 'content' => $source->nodeValue);
+ foreach ($source->attributes as $attribute) {
+ $array[$attribute->name] = $attribute->value;
+ }
+ return $array;
+ }
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Abstract class providing common methods for XML_Feed_Parser feeds.
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category XML
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @copyright 2005 James Stewart <james@jystewart.net>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
+ * @version CVS: $Id: Type.php,v 1.22 2006/08/15 13:02:36 jystewart Exp $
+ * @link http://pear.php.net/package/XML_Feed_Parser/
+ */
+
+/**
+ * This abstract class provides some general methods that are likely to be
+ * implemented exactly the same way for all feed types.
+ *
+ * @package XML_Feed_Parser
+ * @author James Stewart <james@jystewart.net>
+ * @version Release: 1.0.2
+ */
+abstract class XML_Feed_Parser_Type
+{
+ /**
+ * Where we store our DOM object for this feed
+ * @var DOMDocument
+ */
+ public $model;
+
+ /**
+ * For iteration we'll want a count of the number of entries
+ * @var int
+ */
+ public $numberEntries;
+
+ /**
+ * Where we store our entry objects once instantiated
+ * @var array
+ */
+ public $entries = array();
+
+ /**
+ * Proxy to allow use of element names as method names
+ *
+ * We are not going to provide methods for every entry type so this
+ * function will allow for a lot of mapping. We rely pretty heavily
+ * on this to handle our mappings between other feed types and atom.
+ *
+ * @param string $call - the method attempted
+ * @param array $arguments - arguments to that method
+ * @return mixed
+ */
+ function __call($call, $arguments = array())
+ {
+ if (! is_array($arguments)) {
+ $arguments = array();
+ }
+
+ if (isset($this->compatMap[$call])) {
+ $tempMap = $this->compatMap;
+ $tempcall = array_pop($tempMap[$call]);
+ if (! empty($tempMap)) {
+ $arguments = array_merge($arguments, $tempMap[$call]);
+ }
+ $call = $tempcall;
+ }
+
+ /* To be helpful, we allow a case-insensitive search for this method */
+ if (! isset($this->map[$call])) {
+ foreach (array_keys($this->map) as $key) {
+ if (strtoupper($key) == strtoupper($call)) {
+ $call = $key;
+ break;
+ }
+ }
+ }
+
+ if (empty($this->map[$call])) {
+ return false;
+ }
+
+ $method = 'get' . $this->map[$call][0];
+ if ($method == 'getLink') {
+ $offset = empty($arguments[0]) ? 0 : $arguments[0];
+ $attribute = empty($arguments[1]) ? 'href' : $arguments[1];
+ $params = isset($arguments[2]) ? $arguments[2] : array();
+ return $this->getLink($offset, $attribute, $params);
+ }
+ if (method_exists($this, $method)) {
+ return $this->$method($call, $arguments);
+ }
+
+ return false;
+ }
+
+ /**
+ * Proxy to allow use of element names as attribute names
+ *
+ * For many elements variable-style access will be desirable. This function
+ * provides for that.
+ *
+ * @param string $value - the variable required
+ * @return mixed
+ */
+ function __get($value)
+ {
+ return $this->__call($value, array());
+ }
+
+ /**
+ * Utility function to help us resolve xml:base values
+ *
+ * We have other methods which will traverse the DOM and work out the different
+ * xml:base declarations we need to be aware of. We then need to combine them.
+ * If a declaration starts with a protocol then we restart the string. If it
+ * starts with a / then we add on to the domain name. Otherwise we simply tag
+ * it on to the end.
+ *
+ * @param string $base - the base to add the link to
+ * @param string $link
+ */
+ function combineBases($base, $link)
+ {
+ if (preg_match('/^[A-Za-z]+:\/\//', $link)) {
+ return $link;
+ } else if (preg_match('/^\//', $link)) {
+ /* Extract domain and suffix link to that */
+ preg_match('/^([A-Za-z]+:\/\/.*)?\/*/', $base, $results);
+ $firstLayer = $results[0];
+ return $firstLayer . "/" . $link;
+ } else if (preg_match('/^\.\.\//', $base)) {
+ /* Step up link to find place to be */
+ preg_match('/^((\.\.\/)+)(.*)$/', $link, $bases);
+ $suffix = $bases[3];
+ $count = preg_match_all('/\.\.\//', $bases[1], $steps);
+ $url = explode("/", $base);
+ for ($i = 0; $i <= $count; $i++) {
+ array_pop($url);
+ }
+ return implode("/", $url) . "/" . $suffix;
+ } else if (preg_match('/^(?!\/$)/', $base)) {
+ $base = preg_replace('/(.*\/).*$/', '$1', $base) ;
+ return $base . $link;
+ } else {
+ /* Just stick it on the end */
+ return $base . $link;
+ }
+ }
+
+ /**
+ * Determine whether we need to apply our xml:base rules
+ *
+ * Gets us the xml:base data and then processes that with regard
+ * to our current link.
+ *
+ * @param string
+ * @param DOMElement
+ * @return string
+ */
+ function addBase($link, $element)
+ {
+ if (preg_match('/^[A-Za-z]+:\/\//', $link)) {
+ return $link;
+ }
+
+ return $this->combineBases($element->baseURI, $link);
+ }
+
+ /**
+ * Get an entry by its position in the feed, starting from zero
+ *
+ * As well as allowing the items to be iterated over we want to allow
+ * users to be able to access a specific entry. This is one of two ways of
+ * doing that, the other being by ID.
+ *
+ * @param int $offset
+ * @return XML_Feed_Parser_RSS1Element
+ */
+ function getEntryByOffset($offset)
+ {
+ if (! isset($this->entries[$offset])) {
+ $entries = $this->model->getElementsByTagName($this->itemElement);
+ if ($entries->length > $offset) {
+ $xmlBase = $entries->item($offset)->baseURI;
+ $this->entries[$offset] = new $this->itemClass(
+ $entries->item($offset), $this, $xmlBase);
+ if ($id = $this->entries[$offset]->id) {
+ $this->idMappings[$id] = $this->entries[$offset];
+ }
+ } else {
+ throw new XML_Feed_Parser_Exception('No entries found');
+ }
+ }
+
+ return $this->entries[$offset];
+ }
+
+ /**
+ * Return a date in seconds since epoch.
+ *
+ * Get a date construct. We use PHP's strtotime to return it as a unix datetime, which
+ * is the number of seconds since 1970-01-01 00:00:00.
+ *
+ * @link http://php.net/strtotime
+ * @param string $method The name of the date construct we want
+ * @param array $arguments Included for compatibility with our __call usage
+ * @return int|false datetime
+ */
+ protected function getDate($method, $arguments)
+ {
+ $time = $this->model->getElementsByTagName($method);
+ if ($time->length == 0) {
+ return false;
+ }
+ return strtotime($time->item(0)->nodeValue);
+ }
+
+ /**
+ * Get a text construct.
+ *
+ * @param string $method The name of the text construct we want
+ * @param array $arguments Included for compatibility with our __call usage
+ * @return string
+ */
+ protected function getText($method, $arguments = array())
+ {
+ $tags = $this->model->getElementsByTagName($method);
+ if ($tags->length > 0) {
+ $value = $tags->item(0)->nodeValue;
+ return $value;
+ }
+ return false;
+ }
+
+ /**
+ * Apply various rules to retrieve category data.
+ *
+ * There is no single way of declaring a category in RSS1/1.1 as there is in RSS2
+ * and Atom. Instead the usual approach is to use the dublin core namespace to
+ * declare categories. For example delicious use both:
+ * <dc:subject>PEAR</dc:subject> and: <taxo:topics><rdf:Bag>
+ * <rdf:li resource="http://del.icio.us/tag/PEAR" /></rdf:Bag></taxo:topics>
+ * to declare a categorisation of 'PEAR'.
+ *
+ * We need to be sensitive to this where possible.
+ *
+ * @param string $call for compatibility with our overloading
+ * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array
+ * @return string|array|false
+ */
+ protected function getCategory($call, $arguments)
+ {
+ $categories = $this->model->getElementsByTagName('subject');
+ $offset = empty($arguments[0]) ? 0 : $arguments[0];
+ $array = empty($arguments[1]) ? false : true;
+ if ($categories->length <= $offset) {
+ return false;
+ }
+ if ($array) {
+ $list = array();
+ foreach ($categories as $category) {
+ array_push($list, $category->nodeValue);
+ }
+ return $list;
+ }
+ return $categories->item($offset)->nodeValue;
+ }
+
+ /**
+ * Count occurrences of an element
+ *
+ * This function will tell us how many times the element $type
+ * appears at this level of the feed.
+ *
+ * @param string $type the element we want to get a count of
+ * @return int
+ */
+ protected function count($type)
+ {
+ if ($tags = $this->model->getElementsByTagName($type)) {
+ return $tags->length;
+ }
+ return 0;
+ }
+
+ /**
+ * Part of our xml:base processing code
+ *
+ * We need a couple of methods to access XHTML content stored in feeds.
+ * This is because we dereference all xml:base references before returning
+ * the element. This method handles the attributes.
+ *
+ * @param DOMElement $node The DOM node we are iterating over
+ * @return string
+ */
+ function processXHTMLAttributes($node) {
+ $return = '';
+ foreach ($node->attributes as $attribute) {
+ if ($attribute->name == 'src' or $attribute->name == 'href') {
+ $attribute->value = $this->addBase($attribute->value, $attribute);
+ }
+ if ($attribute->name == 'base') {
+ continue;
+ }
+ $return .= $attribute->name . '="' . $attribute->value .'" ';
+ }
+ if (! empty($return)) {
+ return ' ' . trim($return);
+ }
+ return '';
+ }
+
+ /**
+ * Part of our xml:base processing code
+ *
+ * We need a couple of methods to access XHTML content stored in feeds.
+ * This is because we dereference all xml:base references before returning
+ * the element. This method recurs through the tree descending from the node
+ * and builds our string
+ *
+ * @param DOMElement $node The DOM node we are processing
+ * @return string
+ */
+ function traverseNode($node)
+ {
+ $content = '';
+
+ /* Add the opening of this node to the content */
+ if ($node instanceof DOMElement) {
+ $content .= '<' . $node->tagName .
+ $this->processXHTMLAttributes($node) . '>';
+ }
+
+ /* Process children */
+ if ($node->hasChildNodes()) {
+ foreach ($node->childNodes as $child) {
+ $content .= $this->traverseNode($child);
+ }
+ }
+
+ if ($node instanceof DOMText) {
+ $content .= htmlentities($node->nodeValue);
+ }
+
+ /* Add the closing of this node to the content */
+ if ($node instanceof DOMElement) {
+ $content .= '</' . $node->tagName . '>';
+ }
+
+ return $content;
+ }
+
+ /**
+ * Get content from RSS feeds (atom has its own implementation)
+ *
+ * The official way to include full content in an RSS1 entry is to use
+ * the content module's element 'encoded', and RSS2 feeds often duplicate that.
+ * Often, however, the 'description' element is used instead. We will offer that
+ * as a fallback. Atom uses its own approach and overrides this method.
+ *
+ * @return string|false
+ */
+ protected function getContent()
+ {
+ $options = array('encoded', 'description');
+ foreach ($options as $element) {
+ $test = $this->model->getElementsByTagName($element);
+ if ($test->length == 0) {
+ continue;
+ }
+ if ($test->item(0)->hasChildNodes()) {
+ $value = '';
+ foreach ($test->item(0)->childNodes as $child) {
+ if ($child instanceof DOMText) {
+ $value .= $child->nodeValue;
+ } else {
+ $simple = simplexml_import_dom($child);
+ $value .= $simple->asXML();
+ }
+ }
+ return $value;
+ } else if ($test->length > 0) {
+ return $test->item(0)->nodeValue;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if this element has a particular child element.
+ *
+ * @param String
+ * @param Integer
+ * @return bool
+ **/
+ function hasKey($name, $offset = 0)
+ {
+ $search = $this->model->getElementsByTagName($name);
+ return $search->length > $offset;
+ }
+
+ /**
+ * Return an XML serialization of the feed, should it be required. Most
+ * users however, will already have a serialization that they used when
+ * instantiating the object.
+ *
+ * @return string XML serialization of element
+ */
+ function __toString()
+ {
+ $simple = simplexml_import_dom($this->model);
+ return $simple->asXML();
+ }
+
+ /**
+ * Get directory holding RNG schemas. Method is based on that
+ * found in Contact_AddressBook.
+ *
+ * @return string PEAR data directory.
+ * @access public
+ * @static
+ */
+ static function getSchemaDir()
+ {
+ require_once 'PEAR/Config.php';
+ $config = new PEAR_Config;
+ return $config->get('data_dir') . '/XML_Feed_Parser/schemas';
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+ini_set('include_path', get_config('lib'));
+
+function get_config($key) {
+ global $CFG;
+
+ if (array_key_exists($key, $CFG)) {
+ return $CFG[$key];
+ }
+ return false;
+
+}
+
+function get_components() {
+
+ $comps = array();
+ $dirhandle = opendir(get_config('comp'));
+ while (false !== ($file = readdir($dirhandle))) {
+ if (strpos($file, '.') === 0) {
+ continue;
+ }
+ $comps[] = substr($file, 0, strpos($file, '.'));
+ }
+ return $comps;
+}
+
+function parse_rss($url) {
+ require_once('Cache/Lite.php');
+
+ // Set a few options
+ $options = array(
+ 'cacheDir' => get_config('rss_cache'),
+ 'lifeTime' => get_config('cache_lifetime'),
+ 'automaticSerialization' => true,
+ );
+
+ // Create a cache object
+ $cache = new Cache_Lite($options);
+
+ // Test if thereis a valide cache for this id
+ if (!$data = $cache->get($url)) {
+
+ require_once('XML/Feed/Parser.php');
+ require_once('Snoopy.class.php');
+
+ $snoopy = new Snoopy();
+ $snoopy->curl_path = get_config('curl');
+
+ // This is to disable warnings from snoopy while it performs its work.
+ // By all means, comment out while debugging any problems
+ $oldlevel = error_reporting(0);
+ $result = $snoopy->fetch($url);
+ error_reporting($oldlevel);
+
+ if (!$result) {
+ $e = new XML_Feed_Parser_Exception($snoopy->error);
+ $cache->save($e, $url);
+ return false;
+ }
+
+ try {
+ $feed = new XML_Feed_Parser($snoopy->results, false, true, false);
+ }
+ catch (XML_Feed_Parser_Exception $e) {
+ $cache_Lite->save($e, $url);
+ return false;
+ // Don't catch other exceptions, they're an indication something
+ // really bad happened
+ }
+
+ $data = new StdClass;
+ $data->title = $feed->title;
+ $data->url = $url;
+ $data->link = $feed->link;
+ $data->description = $feed->description;
+ $data->content = array();
+ foreach ($feed as $count => $item) {
+ if ($count == 11) { // it starts at one!
+ break;
+ }
+ $data->content[] = (object)array('title' => $item->title, 'link' => $item->link, 'content' => $item->content);
+ }
+ $cache->save($data, $url);
+ }
+
+ if ($data instanceof XML_Feed_Parser_Exception) {
+ return false;
+ }
+ return $data;
+}
+?>
--- /dev/null
+<?php
+/* phpFlickr Class 2.2.0
+ * Written by Dan Coulter (dan@dancoulter.com)
+ * Sourceforge Project Page: http://www.sourceforge.net/projects/phpflickr/
+ * Released under GNU Lesser General Public License (http://www.gnu.org/copyleft/lgpl.html)
+ * For more information about the class and upcoming tools and toys using it,
+ * visit http://www.phpflickr.com/ or http://phpflickr.sourceforge.net
+ *
+ * For installation instructions, open the README.txt file packaged with this
+ * class. If you don't have a copy, you can see it at:
+ * http://www.phpflickr.com/README.txt
+ *
+ * Please submit all problems or questions to the Help Forum on my project page:
+ * http://sourceforge.net/forum/forum.php?forum_id=469652
+ *
+ */
+if (session_id() == "") {
+ @session_start();
+}
+
+// Decides which include path delimiter to use. Windows should be using a semi-colon
+// and everything else should be using a colon. If this isn't working on your system,
+// comment out this if statement and manually set the correct value into $path_delimiter.
+if (strpos(__FILE__, ':') !== false) {
+ $path_delimiter = ';';
+} else {
+ $path_delimiter = ':';
+}
+
+// This will add the packaged PEAR files into the include path for PHP, allowing you
+// to use them transparently. This will prefer officially installed PEAR files if you
+// have them. If you want to prefer the packaged files (there shouldn't be any reason
+// to), swap the two elements around the $path_delimiter variable. If you don't have
+// the PEAR packages installed, you can leave this like it is and move on.
+
+ini_set('include_path', ini_get('include_path') . $path_delimiter . dirname(__FILE__) . '/PEAR');
+
+// If you have problems including the default PEAR install (like if your open_basedir
+// setting doesn't allow you to include files outside of your web root), comment out
+// the line above and uncomment the next line:
+
+// ini_set('include_path', dirname(__FILE__) . '/PEAR' . $path_delimiter . ini_get('include_path'));
+
+class phpFlickr {
+ var $api_key;
+ var $secret;
+ var $REST = 'http://api.flickr.com/services/rest/';
+ var $Upload = 'http://api.flickr.com/services/upload/';
+ var $Replace = 'http://api.flickr.com/services/replace/';
+ var $req;
+ var $response;
+ var $parsed_response;
+ var $cache = false;
+ var $cache_db = null;
+ var $cache_table = null;
+ var $cache_dir = null;
+ var $cache_expire = null;
+ var $die_on_error;
+ var $error_code;
+ Var $error_msg;
+ var $token;
+ var $php_version;
+
+ /*
+ * When your database cache table hits this many rows, a cleanup
+ * will occur to get rid of all of the old rows and cleanup the
+ * garbage in the table. For most personal apps, 1000 rows should
+ * be more than enough. If your site gets hit by a lot of traffic
+ * or you have a lot of disk space to spare, bump this number up.
+ * You should try to set it high enough that the cleanup only
+ * happens every once in a while, so this will depend on the growth
+ * of your table.
+ */
+ var $max_cache_rows = 1000;
+
+ function phpFlickr ($api_key, $secret = NULL, $die_on_error = false)
+ {
+ //The API Key must be set before any calls can be made. You can
+ //get your own at http://www.flickr.com/services/api/misc.api_keys.html
+ $this->api_key = $api_key;
+ $this->secret = $secret;
+ $this->die_on_error = $die_on_error;
+ $this->service = "flickr";
+
+ //Find the PHP version and store it for future reference
+ $this->php_version = explode("-", phpversion());
+ $this->php_version = explode(".", $this->php_version[0]);
+
+ //All calls to the API are done via the POST method using the PEAR::HTTP_Request package.
+ require_once 'HTTP/Request.php';
+ $this->req =& new HTTP_Request();
+ $this->req->setMethod(HTTP_REQUEST_METHOD_POST);
+ }
+
+ function enableCache($type, $connection, $cache_expire = 600, $table = 'flickr_cache')
+ {
+ // Turns on caching. $type must be either "db" (for database caching) or "fs" (for filesystem).
+ // When using db, $connection must be a PEAR::DB connection string. Example:
+ // "mysql://user:password@server/database"
+ // If the $table, doesn't exist, it will attempt to create it.
+ // When using file system, caching, the $connection is the folder that the web server has write
+ // access to. Use absolute paths for best results. Relative paths may have unexpected behavior
+ // when you include this. They'll usually work, you'll just want to test them.
+ if ($type == 'db') {
+ require_once 'DB.php';
+ $db =& DB::connect($connection);
+ if (PEAR::isError($db)) {
+ die($db->getMessage());
+ }
+
+ /*
+ * If high performance is crucial, you can easily comment
+ * out this query once you've created your database table.
+ */
+
+ $db->query("
+ CREATE TABLE IF NOT EXISTS `$table` (
+ `request` CHAR( 35 ) NOT NULL ,
+ `response` MEDIUMTEXT NOT NULL ,
+ `expiration` DATETIME NOT NULL ,
+ INDEX ( `request` )
+ ) TYPE = MYISAM");
+
+ if ($db->getOne("SELECT COUNT(*) FROM $table") > $this->max_cache_rows) {
+ $db->query("DELETE FROM $table WHERE expiration < DATE_SUB(NOW(), INTERVAL $cache_expire second)");
+ $db->query('OPTIMIZE TABLE ' . $this->cache_table);
+ }
+
+ $this->cache = 'db';
+ $this->cache_db = $db;
+ $this->cache_table = $table;
+ } elseif ($type == 'fs') {
+ $this->cache = 'fs';
+ $connection = realpath($connection);
+ $this->cache_dir = $connection;
+ if ($dir = opendir($this->cache_dir)) {
+ while ($file = readdir($dir)) {
+ if (substr($file, -6) == '.cache' && ((filemtime($this->cache_dir . '/' . $file) + $cache_expire) < time()) ) {
+ unlink($this->cache_dir . '/' . $file);
+ }
+ }
+ }
+ }
+ $this->cache_expire = $cache_expire;
+ }
+
+ function getCached ($request)
+ {
+ //Checks the database or filesystem for a cached result to the request.
+ //If there is no cache result, it returns a value of false. If it finds one,
+ //it returns the unparsed XML.
+ $reqhash = md5(serialize($request));
+ if ($this->cache == 'db') {
+ $result = $this->cache_db->getOne("SELECT response FROM " . $this->cache_table . " WHERE request = ? AND DATE_SUB(NOW(), INTERVAL " . (int) $this->cache_expire . " SECOND) < expiration", $reqhash);
+ if (!empty($result)) {
+ return $result;
+ }
+ } elseif ($this->cache == 'fs') {
+ $file = $this->cache_dir . '/' . $reqhash . '.cache';
+ if (file_exists($file)) {
+ if ($this->php_version[0] > 4 || ($this->php_version[0] == 4 && $this->php_version[1] >= 3)) {
+ return file_get_contents($file);
+ } else {
+ return implode('', file($file));
+ }
+ }
+ }
+ return false;
+ }
+
+ function cache ($request, $response)
+ {
+ //Caches the unparsed XML of a request.
+ $reqhash = md5(serialize($request));
+ if ($this->cache == 'db') {
+ //$this->cache_db->query("DELETE FROM $this->cache_table WHERE request = '$reqhash'");
+ if ($this->cache_db->getOne("SELECT COUNT(*) FROM {$this->cache_table} WHERE request = '$reqhash'")) {
+ $sql = "UPDATE " . $this->cache_table . " SET response = ?, expiration = ? WHERE request = ?";
+ $this->cache_db->query($sql, array($response, strftime("%Y-%m-%d %H:%M:%S"), $reqhash));
+ } else {
+ $sql = "INSERT INTO " . $this->cache_table . " (request, response, expiration) VALUES ('$reqhash', '" . str_replace("'", "''", $response) . "', '" . strftime("%Y-%m-%d %H:%M:%S") . "')";
+ $this->cache_db->query($sql);
+ }
+ } elseif ($this->cache == "fs") {
+ $file = $this->cache_dir . "/" . $reqhash . ".cache";
+ $fstream = fopen($file, "w");
+ $result = fwrite($fstream,$response);
+ fclose($fstream);
+ return $result;
+ }
+ return false;
+ }
+
+ function request ($command, $args = array(), $nocache = false)
+ {
+ //Sends a request to Flickr's REST endpoint via POST.
+ $this->req->setURL($this->REST);
+ $this->req->clearPostData();
+ if (substr($command,0,7) != "flickr.") {
+ $command = "flickr." . $command;
+ }
+
+ //Process arguments, including method and login data.
+ $args = array_merge(array("method" => $command, "format" => "php_serial", "api_key" => $this->api_key), $args);
+ if (!empty($this->token)) {
+ $args = array_merge($args, array("auth_token" => $this->token));
+ } elseif (!empty($_SESSION['phpFlickr_auth_token'])) {
+ $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token']));
+ }
+ ksort($args);
+ $auth_sig = "";
+ if (!($this->response = $this->getCached($args)) || $nocache) {
+ foreach ($args as $key => $data) {
+ $auth_sig .= $key . $data;
+ $this->req->addPostData($key, $data);
+ }
+ if (!empty($this->secret)) {
+ $api_sig = md5($this->secret . $auth_sig);
+ $this->req->addPostData("api_sig", $api_sig);
+ }
+
+ $this->req->addHeader("Connection", "Keep-Alive");
+
+ //Send Requests
+ if ($this->req->sendRequest()) {
+ $this->response = $this->req->getResponseBody();
+ $this->cache($args, $this->response);
+ } else {
+ die("There has been a problem sending your command to the server.");
+ }
+ }
+ /*
+ * Uncomment this line (and comment out the next one) if you're doing large queries
+ * and you're concerned about time. This will, however, change the structure of
+ * the result, so be sure that you look at the results.
+ */
+ //$this->parsed_response = unserialize($this->response);
+ $this->parsed_response = $this->clean_text_nodes(unserialize($this->response));
+ if ($this->parsed_response['stat'] == 'fail') {
+ if ($this->die_on_error) die("The Flickr API returned the following error: #{$this->parsed_response['code']} - {$this->parsed_response['message']}");
+ else {
+ $this->error_code = $this->parsed_response['code'];
+ $this->error_msg = $this->parsed_response['message'];
+ $this->parsed_response = false;
+ }
+ } else {
+ $this->error_code = false;
+ $this->error_msg = false;
+ }
+ return $this->response;
+ }
+
+ function clean_text_nodes($arr) {
+ if (!is_array($arr)) {
+ return $arr;
+ } elseif (count($arr) == 0) {
+ return $arr;
+ } elseif (count($arr) == 1 && array_key_exists('_content', $arr)) {
+ return $arr['_content'];
+ } else {
+ foreach ($arr as $key => $element) {
+ $arr[$key] = $this->clean_text_nodes($element);
+ }
+ return($arr);
+ }
+ }
+
+ function setToken($token)
+ {
+ // Sets an authentication token to use instead of the session variable
+ $this->token = $token;
+ }
+
+ function setProxy($server, $port)
+ {
+ // Sets the proxy for all phpFlickr calls.
+ $this->req->setProxy($server, $port);
+ }
+
+ function getErrorCode()
+ {
+ // Returns the error code of the last call. If the last call did not
+ // return an error. This will return a false boolean.
+ return $this->error_code;
+ }
+
+ function getErrorMsg()
+ {
+ // Returns the error message of the last call. If the last call did not
+ // return an error. This will return a false boolean.
+ return $this->error_msg;
+ }
+
+ /* These functions are front ends for the flickr calls */
+
+ function buildPhotoURL ($photo, $size = "Medium")
+ {
+ //receives an array (can use the individual photo data returned
+ //from an API call) and returns a URL (doesn't mean that the
+ //file size exists)
+ $sizes = array(
+ "square" => "_s",
+ "thumbnail" => "_t",
+ "small" => "_m",
+ "medium" => "",
+ "large" => "_b",
+ "original" => "_o"
+ );
+
+ $size = strtolower($size);
+ if (!array_key_exists($size, $sizes)) {
+ $size = "medium";
+ }
+
+ if ($size == "original") {
+ $url = "http://farm" . $photo['farm'] . ".static.flickr.com/" . $photo['server'] . "/" . $photo['id'] . "_" . $photo['originalsecret'] . "_o" . "." . $photo['originalformat'];
+ } else {
+ $url = "http://farm" . $photo['farm'] . ".static.flickr.com/" . $photo['server'] . "/" . $photo['id'] . "_" . $photo['secret'] . $sizes[$size] . ".jpg";
+ }
+ return $url;
+ }
+
+ function getFriendlyGeodata($lat, $lon) {
+ /* I've added this method to get the friendly geodata (i.e. 'in New York, NY') that the
+ * website provides, but isn't available in the API. I'm providing this service as long
+ * as it doesn't flood my server with requests and crash it all the time.
+ */
+ return unserialize(file_get_contents('http://phpflickr.com/geodata/?format=php&lat=' . $lat . '&lon=' . $lon));
+ }
+
+ function sync_upload ($photo, $title = null, $description = null, $tags = null, $is_public = null, $is_friend = null, $is_family = null) {
+ $upload_req =& new HTTP_Request();
+ $upload_req->setMethod(HTTP_REQUEST_METHOD_POST);
+
+
+ $upload_req->setURL($this->Upload);
+ $upload_req->clearPostData();
+
+ //Process arguments, including method and login data.
+ $args = array("api_key" => $this->api_key, "title" => $title, "description" => $description, "tags" => $tags, "is_public" => $is_public, "is_friend" => $is_friend, "is_family" => $is_family);
+ if (!empty($this->email)) {
+ $args = array_merge($args, array("email" => $this->email));
+ }
+ if (!empty($this->password)) {
+ $args = array_merge($args, array("password" => $this->password));
+ }
+ if (!empty($this->token)) {
+ $args = array_merge($args, array("auth_token" => $this->token));
+ } elseif (!empty($_SESSION['phpFlickr_auth_token'])) {
+ $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token']));
+ }
+
+ ksort($args);
+ $auth_sig = "";
+ foreach ($args as $key => $data) {
+ if ($data !== null) {
+ $auth_sig .= $key . $data;
+ $upload_req->addPostData($key, $data);
+ }
+ }
+ if (!empty($this->secret)) {
+ $api_sig = md5($this->secret . $auth_sig);
+ $upload_req->addPostData("api_sig", $api_sig);
+ }
+
+ $photo = realpath($photo);
+
+ $result = $upload_req->addFile("photo", $photo);
+
+ if (PEAR::isError($result)) {
+ die($result->getMessage());
+ }
+
+ //Send Requests
+ if ($upload_req->sendRequest()) {
+ $this->response = $upload_req->getResponseBody();
+ } else {
+ die("There has been a problem sending your command to the server.");
+ }
+
+ $rsp = explode("\n", $this->response);
+ foreach ($rsp as $line) {
+ if (ereg('<err code="([0-9]+)" msg="(.*)"', $line, $match)) {
+ if ($this->die_on_error)
+ die("The Flickr API returned the following error: #{$match[1]} - {$match[2]}");
+ else {
+ $this->error_code = $match[1];
+ $this->error_msg = $match[2];
+ $this->parsed_response = false;
+ return false;
+ }
+ } elseif (ereg("<photoid>(.*)</photoid>", $line, $match)) {
+ $this->error_code = false;
+ $this->error_msg = false;
+ return $match[1];
+ }
+ }
+ }
+
+ function async_upload ($photo, $title = null, $description = null, $tags = null, $is_public = null, $is_friend = null, $is_family = null) {
+ $upload_req =& new HTTP_Request();
+ $upload_req->setMethod(HTTP_REQUEST_METHOD_POST);
+
+ $upload_req->setURL($this->Upload);
+ $upload_req->clearPostData();
+
+ //Process arguments, including method and login data.
+ $args = array("async" => 1, "api_key" => $this->api_key, "title" => $title, "description" => $description, "tags" => $tags, "is_public" => $is_public, "is_friend" => $is_friend, "is_family" => $is_family);
+ if (!empty($this->email)) {
+ $args = array_merge($args, array("email" => $this->email));
+ }
+ if (!empty($this->password)) {
+ $args = array_merge($args, array("password" => $this->password));
+ }
+ if (!empty($this->token)) {
+ $args = array_merge($args, array("auth_token" => $this->token));
+ } elseif (!empty($_SESSION['phpFlickr_auth_token'])) {
+ $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token']));
+ }
+
+ ksort($args);
+ $auth_sig = "";
+ foreach ($args as $key => $data) {
+ if ($data !== null) {
+ $auth_sig .= $key . $data;
+ $upload_req->addPostData($key, $data);
+ }
+ }
+ if (!empty($this->secret)) {
+ $api_sig = md5($this->secret . $auth_sig);
+ $upload_req->addPostData("api_sig", $api_sig);
+ }
+
+ $photo = realpath($photo);
+
+ $result = $upload_req->addFile("photo", $photo);
+
+ if (PEAR::isError($result)) {
+ die($result->getMessage());
+ }
+
+ //Send Requests
+ if ($upload_req->sendRequest()) {
+ $this->response = $upload_req->getResponseBody();
+ } else {
+ die("There has been a problem sending your command to the server.");
+ }
+
+ $rsp = explode("\n", $this->response);
+ foreach ($rsp as $line) {
+ if (ereg('<err code="([0-9]+)" msg="(.*)"', $line, $match)) {
+ if ($this->die_on_error)
+ die("The Flickr API returned the following error: #{$match[1]} - {$match[2]}");
+ else {
+ $this->error_code = $match[1];
+ $this->error_msg = $match[2];
+ $this->parsed_response = false;
+ return false;
+ }
+ } elseif (ereg("<ticketid>(.*)</", $line, $match)) {
+ $this->error_code = false;
+ $this->error_msg = false;
+ return $match[1];
+ }
+ }
+ }
+
+ // Interface for new replace API method.
+ function replace ($photo, $photo_id, $async = null) {
+ $upload_req =& new HTTP_Request();
+ $upload_req->setMethod(HTTP_REQUEST_METHOD_POST);
+
+ $upload_req->setURL($this->Replace);
+ $upload_req->clearPostData();
+
+ //Process arguments, including method and login data.
+ $args = array("api_key" => $this->api_key, "photo_id" => $photo_id, "async" => $async);
+ if (!empty($this->email)) {
+ $args = array_merge($args, array("email" => $this->email));
+ }
+ if (!empty($this->password)) {
+ $args = array_merge($args, array("password" => $this->password));
+ }
+ if (!empty($this->token)) {
+ $args = array_merge($args, array("auth_token" => $this->token));
+ } elseif (!empty($_SESSION['phpFlickr_auth_token'])) {
+ $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token']));
+ }
+
+ ksort($args);
+ $auth_sig = "";
+ foreach ($args as $key => $data) {
+ if ($data !== null) {
+ $auth_sig .= $key . $data;
+ $upload_req->addPostData($key, $data);
+ }
+ }
+ if (!empty($this->secret)) {
+ $api_sig = md5($this->secret . $auth_sig);
+ $upload_req->addPostData("api_sig", $api_sig);
+ }
+
+ $photo = realpath($photo);
+
+ $result = $upload_req->addFile("photo", $photo);
+
+ if (PEAR::isError($result)) {
+ die($result->getMessage());
+ }
+
+ //Send Requests
+ if ($upload_req->sendRequest()) {
+ $this->response = $upload_req->getResponseBody();
+ } else {
+ die("There has been a problem sending your command to the server.");
+ }
+ if ($async == 1)
+ $find = 'ticketid';
+ else
+ $find = 'photoid';
+
+ $rsp = explode("\n", $this->response);
+ foreach ($rsp as $line) {
+ if (ereg('<err code="([0-9]+)" msg="(.*)"', $line, $match)) {
+ if ($this->die_on_error)
+ die("The Flickr API returned the following error: #{$match[1]} - {$match[2]}");
+ else {
+ $this->error_code = $match[1];
+ $this->error_msg = $match[2];
+ $this->parsed_response = false;
+ return false;
+ }
+ } elseif (ereg("<" . $find . ">(.*)</", $line, $match)) {
+ $this->error_code = false;
+ $this->error_msg = false;
+ return $match[1];
+ }
+ }
+ }
+
+ function auth ($perms = "read", $remember_uri = true)
+ {
+ // Redirects to Flickr's authentication piece if there is no valid token.
+ // If remember_uri is set to false, the callback script (included) will
+ // redirect to its default page.
+
+ if (empty($_SESSION['phpFlickr_auth_token']) && empty($this->token)) {
+ if ($remember_uri) {
+ $redirect = $_SERVER['REQUEST_URI'];
+ }
+ $api_sig = md5($this->secret . "api_key" . $this->api_key . "extra" . $redirect . "perms" . $perms);
+ if ($this->service == "23") {
+ header("Location: http://www.23hq.com/services/auth/?api_key=" . $this->api_key . "&extra=" . $redirect . "&perms=" . $perms . "&api_sig=". $api_sig);
+ } else {
+ header("Location: http://www.flickr.com/services/auth/?api_key=" . $this->api_key . "&extra=" . $redirect . "&perms=" . $perms . "&api_sig=". $api_sig);
+ }
+ exit;
+ } else {
+ $tmp = $this->die_on_error;
+ $this->die_on_error = false;
+ $rsp = $this->auth_checkToken();
+ if ($this->error_code !== false) {
+ unset($_SESSION['phpFlickr_auth_token']);
+ $this->auth($perms, $remember_uri);
+ }
+ $this->die_on_error = $tmp;
+ return $rsp['perms'];
+ }
+ }
+
+ /*******************************
+
+ To use the phpFlickr::call method, pass a string containing the API method you want
+ to use and an associative array of arguments. For example:
+ $result = $f->call("flickr.photos.comments.getList", array("photo_id"=>'34952612'));
+ This method will allow you to make calls to arbitrary methods that haven't been
+ implemented in phpFlickr yet.
+
+ *******************************/
+
+ function call($method, $arguments)
+ {
+ $this->request($method, $arguments);
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ /*
+ These functions are the direct implementations of flickr calls.
+ For method documentation, including arguments, visit the address
+ included in a comment in the function.
+ */
+
+ /* Activity methods */
+ function activity_userComments ($per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.activity.userComments.html */
+ $this->request('flickr.activity.userComments', array("per_page" => $per_page, "page" => $page));
+ return $this->parsed_response ? $this->parsed_response['items']['item'] : false;
+ }
+
+ function activity_userPhotos ($timeframe = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.activity.userPhotos.html */
+ $this->request('flickr.activity.userPhotos', array("timeframe" => $timeframe, "per_page" => $per_page, "page" => $page));
+ return $this->parsed_response ? $this->parsed_response['items']['item'] : false;
+ }
+
+ /* Authentication methods */
+ function auth_checkToken ()
+ {
+ /* http://www.flickr.com/services/api/flickr.auth.checkToken.html */
+ $this->request('flickr.auth.checkToken');
+ return $this->parsed_response ? $this->parsed_response['auth'] : false;
+ }
+
+ function auth_getFrob ()
+ {
+ /* http://www.flickr.com/services/api/flickr.auth.getFrob.html */
+ $this->request('flickr.auth.getFrob');
+ return $this->parsed_response ? $this->parsed_response['frob'] : false;
+ }
+
+ function auth_getFullToken ($mini_token)
+ {
+ /* http://www.flickr.com/services/api/flickr.auth.getFullToken.html */
+ $this->request('flickr.auth.getFullToken', array('mini_token'=>$mini_token));
+ return $this->parsed_response ? $this->parsed_response['auth'] : false;
+ }
+
+ function auth_getToken ($frob)
+ {
+ /* http://www.flickr.com/services/api/flickr.auth.getToken.html */
+ $this->request('flickr.auth.getToken', array('frob'=>$frob));
+ session_register('phpFlickr_auth_token');
+ $_SESSION['phpFlickr_auth_token'] = $this->parsed_response['auth']['token'];
+ return $this->parsed_response ? $this->parsed_response['auth'] : false;
+ }
+
+ /* Blogs methods */
+ function blogs_getList ()
+ {
+ /* http://www.flickr.com/services/api/flickr.blogs.getList.html */
+ $this->request('flickr.blogs.getList');
+ return $this->parsed_response ? $this->parsed_response['blogs']['blog'] : false;
+ }
+
+ function blogs_postPhoto($blog_id, $photo_id, $title, $description, $blog_password = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.blogs.postPhoto.html */
+ $this->request('flickr.blogs.postPhoto', array('blog_id'=>$blog_id, 'photo_id'=>$photo_id, 'title'=>$title, 'description'=>$description, 'blog_password'=>$blog_password), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Contacts Methods */
+ function contacts_getList ($filter = NULL, $page = NULL, $per_page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.contacts.getList.html */
+ $this->request('flickr.contacts.getList', array('filter'=>$filter, 'page'=>$page, 'per_page'=>$per_page));
+ return $this->parsed_response ? $this->parsed_response['contacts'] : false;
+ }
+
+ function contacts_getPublicList($user_id, $page = NULL, $per_page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.contacts.getPublicList.html */
+ $this->request('flickr.contacts.getPublicList', array('user_id'=>$user_id, 'page'=>$page, 'per_page'=>$per_page));
+ return $this->parsed_response ? $this->parsed_response['contacts'] : false;
+ }
+
+ /* Favorites Methods */
+ function favorites_add ($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.favorites.add.html */
+ $this->request('flickr.favorites.add', array('photo_id'=>$photo_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function favorites_getList($user_id = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.favorites.getList.html */
+ if (is_array($extras)) { $extras = implode(",", $extras); }
+ $this->request("flickr.favorites.getList", array("user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function favorites_getPublicList($user_id = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.favorites.getPublicList.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+ $this->request("flickr.favorites.getPublicList", array("user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function favorites_remove($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.favorites.remove.html */
+ $this->request("flickr.favorites.remove", array("photo_id"=>$photo_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Groups Methods */
+ function groups_browse ($cat_id = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.browse.html */
+ $this->request("flickr.groups.browse", array("cat_id"=>$cat_id));
+ return $this->parsed_response ? $this->parsed_response['category'] : false;
+ }
+
+ function groups_getInfo ($group_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.getInfo.html */
+ $this->request("flickr.groups.getInfo", array("group_id"=>$group_id));
+ return $this->parsed_response ? $this->parsed_response['group'] : false;
+ }
+
+ function groups_search ($text, $per_page=NULL, $page=NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.search.html */
+ $this->request("flickr.groups.search", array("text"=>$text,"per_page"=>$per_page,"page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['groups'] : false;
+ }
+
+ /* Groups Pools Methods */
+ function groups_pools_add ($photo_id, $group_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.pools.add.html */
+ $this->request("flickr.groups.pools.add", array("photo_id"=>$photo_id, "group_id"=>$group_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function groups_pools_getContext ($photo_id, $group_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.pools.getContext.html */
+ $this->request("flickr.groups.pools.getContext", array("photo_id"=>$photo_id, "group_id"=>$group_id));
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ function groups_pools_getGroups ($page = NULL, $per_page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.pools.getGroups.html */
+ $this->request("flickr.groups.pools.getGroups", array('page'=>$page, 'per_page'=>$per_page));
+ return $this->parsed_response ? $this->parsed_response['groups'] : false;
+ }
+
+ function groups_pools_getPhotos ($group_id, $tags = NULL, $user_id = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.pools.getPhotos.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+ $this->request("flickr.groups.pools.getPhotos", array("group_id"=>$group_id, "tags"=>$tags, "user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function groups_pools_remove ($photo_id, $group_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.groups.pools.remove.html */
+ $this->request("flickr.groups.pools.remove", array("photo_id"=>$photo_id, "group_id"=>$group_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Interestingness methods */
+ function interestingness_getList($date = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.interestingness.getList.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+
+ $this->request("flickr.interestingness.getList", array("date"=>$date, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ /* People methods */
+ function people_findByEmail ($find_email)
+ {
+ /* http://www.flickr.com/services/api/flickr.people.findByEmail.html */
+ $this->request("flickr.people.findByEmail", array("find_email"=>$find_email));
+ return $this->parsed_response ? $this->parsed_response['user'] : false;
+ }
+
+ function people_findByUsername ($username)
+ {
+ /* http://www.flickr.com/services/api/flickr.people.findByUsername.html */
+ $this->request("flickr.people.findByUsername", array("username"=>$username));
+ return $this->parsed_response ? $this->parsed_response['user'] : false;
+ }
+
+ function people_getInfo($user_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.people.getInfo.html */
+ $this->request("flickr.people.getInfo", array("user_id"=>$user_id));
+ return $this->parsed_response ? $this->parsed_response['person'] : false;
+ }
+
+ function people_getPublicGroups($user_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.people.getPublicGroups.html */
+ $this->request("flickr.people.getPublicGroups", array("user_id"=>$user_id));
+ return $this->parsed_response ? $this->parsed_response['groups']['group'] : false;
+ }
+
+ function people_getPublicPhotos($user_id, $extras = NULL, $per_page = NULL, $page = NULL) {
+ /* http://www.flickr.com/services/api/flickr.people.getPublicPhotos.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+
+ $this->request("flickr.people.getPublicPhotos", array("user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function people_getUploadStatus()
+ {
+ /* http://www.flickr.com/services/api/flickr.people.getUploadStatus.html */
+ /* Requires Authentication */
+ $this->request("flickr.people.getUploadStatus");
+ return $this->parsed_response ? $this->parsed_response['user'] : false;
+ }
+
+
+ /* Photos Methods */
+ function photos_addTags ($photo_id, $tags)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.addTags.html */
+ $this->request("flickr.photos.addTags", array("photo_id"=>$photo_id, "tags"=>$tags), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_delete($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.delete.html */
+ $this->request("flickr.photos.delete", array("photo_id"=>$photo_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_getAllContexts ($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getAllContexts.html */
+ $this->request("flickr.photos.getAllContexts", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ function photos_getContactsPhotos ($count = NULL, $just_friends = NULL, $single_photo = NULL, $include_self = NULL, $extras = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getContactsPhotos.html */
+ $this->request("flickr.photos.getContactsPhotos", array("count"=>$count, "just_friends"=>$just_friends, "single_photo"=>$single_photo, "include_self"=>$include_self, "extras"=>$extras));
+ return $this->parsed_response ? $this->parsed_response['photos']['photo'] : false;
+ }
+
+ function photos_getContactsPublicPhotos ($user_id, $count = NULL, $just_friends = NULL, $single_photo = NULL, $include_self = NULL, $extras = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getContactsPublicPhotos.html */
+ $this->request("flickr.photos.getContactsPublicPhotos", array("user_id"=>$user_id, "count"=>$count, "just_friends"=>$just_friends, "single_photo"=>$single_photo, "include_self"=>$include_self, "extras"=>$extras));
+ return $this->parsed_response ? $this->parsed_response['photos']['photo'] : false;
+ }
+
+ function photos_getContext ($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getContext.html */
+ $this->request("flickr.photos.getContext", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ function photos_getCounts ($dates = NULL, $taken_dates = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getCounts.html */
+ $this->request("flickr.photos.getCounts", array("dates"=>$dates, "taken_dates"=>$taken_dates));
+ return $this->parsed_response ? $this->parsed_response['photocounts']['photocount'] : false;
+ }
+
+ function photos_getExif ($photo_id, $secret = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getExif.html */
+ $this->request("flickr.photos.getExif", array("photo_id"=>$photo_id, "secret"=>$secret));
+ return $this->parsed_response ? $this->parsed_response['photo'] : false;
+ }
+
+ function photos_getFavorites($photo_id, $page = NULL, $per_page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getFavorites.html */
+ $this->request("flickr.photos.getFavorites", array("photo_id"=>$photo_id, "page"=>$page, "per_page"=>$per_page));
+ return $this->parsed_response ? $this->parsed_response['photo'] : false;
+ }
+
+ function photos_getInfo($photo_id, $secret = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getInfo.html */
+ $this->request("flickr.photos.getInfo", array("photo_id"=>$photo_id, "secret"=>$secret));
+ return $this->parsed_response ? $this->parsed_response['photo'] : false;
+ }
+
+ function photos_getNotInSet($min_upload_date = NULL, $max_upload_date = NULL, $min_taken_date = NULL, $max_taken_date = NULL, $privacy_filter = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getNotInSet.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+ $this->request("flickr.photos.getNotInSet", array("min_upload_date"=>$min_upload_date, "max_upload_date"=>$max_upload_date, "min_taken_date"=>$min_taken_date, "max_taken_date"=>$max_taken_date, "privacy_filter"=>$privacy_filter, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_getPerms($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getPerms.html */
+ $this->request("flickr.photos.getPerms", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response['perms'] : false;
+ }
+
+ function photos_getRecent($extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getRecent.html */
+
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+ $this->request("flickr.photos.getRecent", array("extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_getSizes($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getSizes.html */
+ $this->request("flickr.photos.getSizes", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response['sizes']['size'] : false;
+ }
+
+ function photos_getUntagged($min_upload_date = NULL, $max_upload_date = NULL, $min_taken_date = NULL, $max_taken_date = NULL, $privacy_filter = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getUntagged.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+ $this->request("flickr.photos.getUntagged", array("min_upload_date"=>$min_upload_date, "max_upload_date"=>$max_upload_date, "min_taken_date"=>$min_taken_date, "max_taken_date"=>$max_taken_date, "privacy_filter"=>$privacy_filter, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_getWithGeoData($args = NULL) {
+ /* See the documentation included with the photos_search() function.
+ * I'm using the same style of arguments for this function. The only
+ * difference here is that this doesn't require any arguments. The
+ * flickr.photos.search method requires at least one search parameter.
+ */
+ /* http://www.flickr.com/services/api/flickr.photos.getWithGeoData.html */
+ if (is_null($args)) {
+ $args = array();
+ }
+ $this->request("flickr.photos.getWithGeoData", $args);
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_getWithoutGeoData($args = NULL) {
+ /* See the documentation included with the photos_search() function.
+ * I'm using the same style of arguments for this function. The only
+ * difference here is that this doesn't require any arguments. The
+ * flickr.photos.search method requires at least one search parameter.
+ */
+ /* http://www.flickr.com/services/api/flickr.photos.getWithoutGeoData.html */
+ if (is_null($args)) {
+ $args = array();
+ }
+ $this->request("flickr.photos.getWithoutGeoData", $args);
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_recentlyUpdated($min_date = NULL, $extras = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.getUntagged.html */
+ if (is_array($extras)) {
+ $extras = implode(",", $extras);
+ }
+ $this->request("flickr.photos.recentlyUpdated", array("min_date"=>$min_date, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page));
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_removeTag($tag_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.removeTag.html */
+ $this->request("flickr.photos.removeTag", array("tag_id"=>$tag_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_search($args)
+ {
+ /* This function strays from the method of arguments that I've
+ * used in the other functions for the fact that there are just
+ * so many arguments to this API method. What you'll need to do
+ * is pass an associative array to the function containing the
+ * arguments you want to pass to the API. For example:
+ * $photos = $f->photos_search(array("tags"=>"brown,cow", "tag_mode"=>"any"));
+ * This will return photos tagged with either "brown" or "cow"
+ * or both. See the API documentation (link below) for a full
+ * list of arguments.
+ */
+
+ /* http://www.flickr.com/services/api/flickr.photos.search.html */
+ $this->request("flickr.photos.search", $args);
+ return $this->parsed_response ? $this->parsed_response['photos'] : false;
+ }
+
+ function photos_setContentType ($photo_id, $content_type) {
+ /* http://www.flickr.com/services/api/flickr.photos.setContentType.html */
+ return $this->call('flickr.photos.setContentType', array('photo_id' => $photo_id, 'content_type' => $content_type));
+ }
+
+ function photos_setDates($photo_id, $date_posted = NULL, $date_taken = NULL, $date_taken_granularity = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.setDates.html */
+ $this->request("flickr.photos.setDates", array("photo_id"=>$photo_id, "date_posted"=>$date_posted, "date_taken"=>$date_taken, "date_taken_granularity"=>$date_taken_granularity), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_setMeta($photo_id, $title, $description)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.setMeta.html */
+ $this->request("flickr.photos.setMeta", array("photo_id"=>$photo_id, "title"=>$title, "description"=>$description), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_setPerms($photo_id, $is_public, $is_friend, $is_family, $perm_comment, $perm_addmeta)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.setPerms.html */
+ $this->request("flickr.photos.setPerms", array("photo_id"=>$photo_id, "is_public"=>$is_public, "is_friend"=>$is_friend, "is_family"=>$is_family, "perm_comment"=>$perm_comment, "perm_addmeta"=>$perm_addmeta), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_setSafetyLevel ($photo_id, $safety_level, $hidden = null) {
+ /* http://www.flickr.com/services/api/flickr.photos.setSafetyLevel.html */
+ return $this->call('flickr.photos.setSafetyLevel', array('photo_id' => $photo_id, 'safety_level' => $safety_level, 'hidden' => $hidden));
+ }
+
+
+ function photos_setTags($photo_id, $tags)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.setTags.html */
+ $this->request("flickr.photos.setTags", array("photo_id"=>$photo_id, "tags"=>$tags), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Photos - Comments Methods */
+ function photos_comments_addComment($photo_id, $comment_text) {
+ /* http://www.flickr.com/services/api/flickr.photos.comments.addComment.html */
+ $this->request("flickr.photos.comments.addComment", array("photo_id" => $photo_id, "comment_text"=>$comment_text), TRUE);
+ return $this->parsed_response ? $this->parsed_response['comment'] : false;
+ }
+
+ function photos_comments_deleteComment($comment_id) {
+ /* http://www.flickr.com/services/api/flickr.photos.comments.deleteComment.html */
+ $this->request("flickr.photos.comments.deleteComment", array("comment_id" => $comment_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_comments_editComment($comment_id, $comment_text) {
+ /* http://www.flickr.com/services/api/flickr.photos.comments.editComment.html */
+ $this->request("flickr.photos.comments.editComment", array("comment_id" => $comment_id, "comment_text"=>$comment_text), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_comments_getList($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.comments.getList.html */
+ $this->request("flickr.photos.comments.getList", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response['comments'] : false;
+ }
+
+ /* Photos - Geo Methods */
+ function photos_geo_getLocation($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.geo.getLocation.html */
+ $this->request("flickr.photos.geo.getLocation", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response['photo'] : false;
+ }
+
+ function photos_geo_getPerms($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.geo.getPerms.html */
+ $this->request("flickr.photos.geo.getPerms", array("photo_id"=>$photo_id));
+ return $this->parsed_response ? $this->parsed_response['perms'] : false;
+ }
+
+ function photos_geo_removeLocation($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.geo.removeLocation.html */
+ $this->request("flickr.photos.geo.removeLocation", array("photo_id"=>$photo_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_geo_setLocation($photo_id, $lat, $lon, $accuracy = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.geo.setLocation.html */
+ $this->request("flickr.photos.geo.setLocation", array("photo_id"=>$photo_id, "lat"=>$lat, "lon"=>$lon, "accuracy"=>$accuracy), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_geo_setPerms($photo_id, $is_public, $is_contact, $is_friend, $is_family)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.geo.setPerms.html */
+ $this->request("flickr.photos.geo.setPerms", array("photo_id"=>$photo_id, "is_public"=>$is_public, "is_contact"=>$is_contact, "is_friend"=>$is_friend, "is_family"=>$is_family), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Photos - Licenses Methods */
+ function photos_licenses_getInfo()
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.licenses.getInfo.html */
+ $this->request("flickr.photos.licenses.getInfo");
+ return $this->parsed_response ? $this->parsed_response['licenses']['license'] : false;
+ }
+
+ function photos_licenses_setLicense($photo_id, $license_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.licenses.setLicense.html */
+ /* Requires Authentication */
+ $this->request("flickr.photos.licenses.setLicense", array("photo_id"=>$photo_id, "license_id"=>$license_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Photos - Notes Methods */
+ function photos_notes_add($photo_id, $note_x, $note_y, $note_w, $note_h, $note_text)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.notes.add.html */
+ $this->request("flickr.photos.notes.add", array("photo_id" => $photo_id, "note_x" => $note_x, "note_y" => $note_y, "note_w" => $note_w, "note_h" => $note_h, "note_text" => $note_text), TRUE);
+ return $this->parsed_response ? $this->parsed_response['note'] : false;
+ }
+
+ function photos_notes_delete($note_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.notes.delete.html */
+ $this->request("flickr.photos.notes.delete", array("note_id" => $note_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photos_notes_edit($note_id, $note_x, $note_y, $note_w, $note_h, $note_text)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.notes.edit.html */
+ $this->request("flickr.photos.notes.edit", array("note_id" => $note_id, "note_x" => $note_x, "note_y" => $note_y, "note_w" => $note_w, "note_h" => $note_h, "note_text" => $note_text), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Photos - Transform Methods */
+ function photos_transform_rotate($photo_id, $degrees)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.transform.rotate.html */
+ $this->request("flickr.photos.transform.rotate", array("photo_id" => $photo_id, "degrees" => $degrees), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Photos - Upload Methods */
+ function photos_upload_checkTickets($tickets)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.upload.checkTickets.html */
+ if (is_array($tickets)) {
+ $tickets = implode(",", $tickets);
+ }
+ $this->request("flickr.photos.upload.checkTickets", array("tickets" => $tickets), TRUE);
+ return $this->parsed_response ? $this->parsed_response['uploader']['ticket'] : false;
+ }
+
+ /* Photosets Methods */
+ function photosets_addPhoto($photoset_id, $photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.addPhoto.html */
+ $this->request("flickr.photosets.addPhoto", array("photoset_id" => $photoset_id, "photo_id" => $photo_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_create($title, $description, $primary_photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.create.html */
+ $this->request("flickr.photosets.create", array("title" => $title, "primary_photo_id" => $primary_photo_id, "description" => $description), TRUE);
+ return $this->parsed_response ? $this->parsed_response['photoset'] : false;
+ }
+
+ function photosets_delete($photoset_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.delete.html */
+ $this->request("flickr.photosets.delete", array("photoset_id" => $photoset_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_editMeta($photoset_id, $title, $description = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.editMeta.html */
+ $this->request("flickr.photosets.editMeta", array("photoset_id" => $photoset_id, "title" => $title, "description" => $description), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_editPhotos($photoset_id, $primary_photo_id, $photo_ids)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.editPhotos.html */
+ $this->request("flickr.photosets.editPhotos", array("photoset_id" => $photoset_id, "primary_photo_id" => $primary_photo_id, "photo_ids" => $photo_ids), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_getContext($photo_id, $photoset_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.getContext.html */
+ $this->request("flickr.photosets.getContext", array("photo_id" => $photo_id, "photoset_id" => $photoset_id));
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ function photosets_getInfo($photoset_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.getInfo.html */
+ $this->request("flickr.photosets.getInfo", array("photoset_id" => $photoset_id));
+ return $this->parsed_response ? $this->parsed_response['photoset'] : false;
+ }
+
+ function photosets_getList($user_id = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.getList.html */
+ $this->request("flickr.photosets.getList", array("user_id" => $user_id));
+ return $this->parsed_response ? $this->parsed_response['photosets'] : false;
+ }
+
+ function photosets_getPhotos($photoset_id, $extras = NULL, $privacy_filter = NULL, $per_page = NULL, $page = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.getPhotos.html */
+ $this->request("flickr.photosets.getPhotos", array("photoset_id" => $photoset_id, "extras" => $extras, "privacy_filter" => $privacy_filter, "per_page" => $per_page, "page" => $page));
+ return $this->parsed_response ? $this->parsed_response['photoset'] : false;
+ }
+
+ function photosets_orderSets($photoset_ids)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.orderSets.html */
+ if (is_array($photoset_ids)) {
+ $photoset_ids = implode(",", $photoset_ids);
+ }
+ $this->request("flickr.photosets.orderSets", array("photoset_ids" => $photoset_ids), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_removePhoto($photoset_id, $photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.removePhoto.html */
+ $this->request("flickr.photosets.removePhoto", array("photoset_id" => $photoset_id, "photo_id" => $photo_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ /* Photosets Comments Methods */
+ function photosets_comments_addComment($photoset_id, $comment_text) {
+ /* http://www.flickr.com/services/api/flickr.photosets.comments.addComment.html */
+ $this->request("flickr.photosets.comments.addComment", array("photoset_id" => $photoset_id, "comment_text"=>$comment_text), TRUE);
+ return $this->parsed_response ? $this->parsed_response['comment'] : false;
+ }
+
+ function photosets_comments_deleteComment($comment_id) {
+ /* http://www.flickr.com/services/api/flickr.photosets.comments.deleteComment.html */
+ $this->request("flickr.photosets.comments.deleteComment", array("comment_id" => $comment_id), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_comments_editComment($comment_id, $comment_text) {
+ /* http://www.flickr.com/services/api/flickr.photosets.comments.editComment.html */
+ $this->request("flickr.photosets.comments.editComment", array("comment_id" => $comment_id, "comment_text"=>$comment_text), TRUE);
+ return $this->parsed_response ? true : false;
+ }
+
+ function photosets_comments_getList($photoset_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.photosets.comments.getList.html */
+ $this->request("flickr.photosets.comments.getList", array("photoset_id"=>$photoset_id));
+ return $this->parsed_response ? $this->parsed_response['comments'] : false;
+ }
+
+ /* Places Methods */
+ function places_resolvePlaceId ($place_id) {
+ /* http://www.flickr.com/services/api/flickr.places.resolvePlaceId.html */
+ $rsp = $this->call('flickr.places.resolvePlaceId', array('place_id' => $place_id));
+ return $rsp ? $rsp['location'] : $rsp;
+ }
+
+ function places_resolvePlaceURL ($url) {
+ /* http://www.flickr.com/services/api/flickr.places.resolvePlaceURL.html */
+ $rsp = $this->call('flickr.places.resolvePlaceURL', array('url' => $url));
+ return $rsp ? $rsp['location'] : $rsp;
+ }
+
+ /* Prefs Methods */
+ function prefs_getContentType () {
+ /* http://www.flickr.com/services/api/flickr.prefs.getContentType.html */
+ $rsp = $this->call('flickr.prefs.getContentType', array());
+ return $rsp ? $rsp['person'] : $rsp;
+ }
+
+ function prefs_getHidden () {
+ /* http://www.flickr.com/services/api/flickr.prefs.getHidden.html */
+ $rsp = $this->call('flickr.prefs.getHidden', array());
+ return $rsp ? $rsp['person'] : $rsp;
+ }
+
+ function prefs_getPrivacy () {
+ /* http://www.flickr.com/services/api/flickr.prefs.getPrivacy.html */
+ $rsp = $this->call('flickr.prefs.getPrivacy', array());
+ return $rsp ? $rsp['person'] : $rsp;
+ }
+
+ function prefs_getSafetyLevel () {
+ /* http://www.flickr.com/services/api/flickr.prefs.getSafetyLevel.html */
+ $rsp = $this->call('flickr.prefs.getSafetyLevel', array());
+ return $rsp ? $rsp['person'] : $rsp;
+ }
+
+ /* Reflection Methods */
+ function reflection_getMethodInfo($method_name)
+ {
+ /* http://www.flickr.com/services/api/flickr.reflection.getMethodInfo.html */
+ $this->request("flickr.reflection.getMethodInfo", array("method_name" => $method_name));
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ function reflection_getMethods()
+ {
+ /* http://www.flickr.com/services/api/flickr.reflection.getMethods.html */
+ $this->request("flickr.reflection.getMethods");
+ return $this->parsed_response ? $this->parsed_response['methods']['method'] : false;
+ }
+
+ /* Tags Methods */
+ function tags_getHotList($period = NULL, $count = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.tags.getHotList.html */
+ $this->request("flickr.tags.getHotList", array("period" => $period, "count" => $count));
+ return $this->parsed_response ? $this->parsed_response['hottags'] : false;
+ }
+
+ function tags_getListPhoto($photo_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.tags.getListPhoto.html */
+ $this->request("flickr.tags.getListPhoto", array("photo_id" => $photo_id));
+ return $this->parsed_response ? $this->parsed_response['photo']['tags']['tag'] : false;
+ }
+
+ function tags_getListUser($user_id = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.tags.getListUser.html */
+ $this->request("flickr.tags.getListUser", array("user_id" => $user_id));
+ return $this->parsed_response ? $this->parsed_response['who']['tags']['tag'] : false;
+ }
+
+ function tags_getListUserPopular($user_id = NULL, $count = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.tags.getListUserPopular.html */
+ $this->request("flickr.tags.getListUserPopular", array("user_id" => $user_id, "count" => $count));
+ return $this->parsed_response ? $this->parsed_response['who']['tags']['tag'] : false;
+ }
+
+ function tags_getListUserRaw($tag)
+ {
+ /* http://www.flickr.com/services/api/flickr.tags.getListUserRaw.html */
+ $this->request("flickr.tags.getListUserRaw", array("tag" => $tag));
+ return $this->parsed_response ? $this->parsed_response['who']['tags']['tag'][0]['raw'] : false;
+ }
+
+ function tags_getRelated($tag)
+ {
+ /* http://www.flickr.com/services/api/flickr.tags.getRelated.html */
+ $this->request("flickr.tags.getRelated", array("tag" => $tag));
+ return $this->parsed_response ? $this->parsed_response['tags'] : false;
+ }
+
+ function test_echo($args = array())
+ {
+ /* http://www.flickr.com/services/api/flickr.test.echo.html */
+ $this->request("flickr.test.echo", $args);
+ return $this->parsed_response ? $this->parsed_response : false;
+ }
+
+ function test_login()
+ {
+ /* http://www.flickr.com/services/api/flickr.test.login.html */
+ $this->request("flickr.test.login");
+ return $this->parsed_response ? $this->parsed_response['user'] : false;
+ }
+
+ function urls_getGroup($group_id)
+ {
+ /* http://www.flickr.com/services/api/flickr.urls.getGroup.html */
+ $this->request("flickr.urls.getGroup", array("group_id"=>$group_id));
+ return $this->parsed_response ? $this->parsed_response['group']['url'] : false;
+ }
+
+ function urls_getUserPhotos($user_id = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.urls.getUserPhotos.html */
+ $this->request("flickr.urls.getUserPhotos", array("user_id"=>$user_id));
+ return $this->parsed_response ? $this->parsed_response['user']['url'] : false;
+ }
+
+ function urls_getUserProfile($user_id = NULL)
+ {
+ /* http://www.flickr.com/services/api/flickr.urls.getUserProfile.html */
+ $this->request("flickr.urls.getUserProfile", array("user_id"=>$user_id));
+ return $this->parsed_response ? $this->parsed_response['user']['url'] : false;
+ }
+
+ function urls_lookupGroup($url)
+ {
+ /* http://www.flickr.com/services/api/flickr.urls.lookupGroup.html */
+ $this->request("flickr.urls.lookupGroup", array("url"=>$url));
+ return $this->parsed_response ? $this->parsed_response['group'] : false;
+ }
+
+ function urls_lookupUser($url)
+ {
+ /* http://www.flickr.com/services/api/flickr.photos.notes.edit.html */
+ $this->request("flickr.urls.lookupUser", array("url"=>$url));
+ return $this->parsed_response ? $this->parsed_response['user'] : false;
+ }
+}
+
+
+?>
--- /dev/null
+body {
+ background-color:#aaaaaa;
+ padding:0px;
+ margin:0px;
+ font-family:verdana,arial,helvetica,sans-serif;
+ font-size:10px;
+ color:black;
+}
+
+div.component {
+ position:absolute;
+ border:1px solid black;
+ background-color:white;
+ margin:0px;
+}
+
+p, span {
+ margin:8px;
+}
+
+ul {
+ margin-left: 0px;
+ padding-left: 0px;
+}
+
+li {
+ margin-left: 0px;
+ list-style: none;
+ margin:8px;
+}
+
+li.last {
+ margin-top:20px;
+ text-align:right;
+}
+
+h2 {
+ margin-left:14px;
+ font-size:14px;
+ color:#336699;
+ border-bottom:1px dashed #aaaaaa;
+}
+
+a {
+ color:#888888;
+ text-decoration:none;
+}
+
+a:hover {
+ color:#336699;
+ text-decoration:underline;
+}
+
+.mind {
+ border:1px dashed #aaaaaa;
+ padding:5px;
+ display:block;
+}
+
+div#flickr {
+ left:20px;
+ top:20px;
+ padding:5px;
+ width:770px;
+ text-align:center;
+}
+div#flickr img {
+ margin:5px;
+}
+
+div#main {
+ left:20px;
+ width:780px;
+ height:195px;
+ top:135px;
+}
+
+div#blog {
+ top:350px;
+ left:20px;
+ width:220px;
+}
+
+div#twitter {
+ top:350px;
+ left:260px;
+ width:300px;
+}
+
+div#lastfm{
+ top:350px;
+ left:580px;
+ width:220px;
+}