]> git.mjollnir.org Git - moodle.git/commitdiff
"MDL-15488, flickr plug-in added"
authordongsheng <dongsheng>
Tue, 1 Jul 2008 07:37:41 +0000 (07:37 +0000)
committerdongsheng <dongsheng>
Tue, 1 Jul 2008 07:37:41 +0000 (07:37 +0000)
15 files changed:
repository/boxnet/repository.class.php
repository/flickr/PEAR/DB.php [new file with mode: 0755]
repository/flickr/PEAR/DB/common.php [new file with mode: 0755]
repository/flickr/PEAR/DB/mysql.php [new file with mode: 0755]
repository/flickr/PEAR/DB/pgsql.php [new file with mode: 0755]
repository/flickr/PEAR/DB/storage.php [new file with mode: 0755]
repository/flickr/PEAR/HTTP/Request.php [new file with mode: 0755]
repository/flickr/PEAR/HTTP/Request/Listener.php [new file with mode: 0755]
repository/flickr/PEAR/Net/Socket.php [new file with mode: 0755]
repository/flickr/PEAR/Net/URL.php [new file with mode: 0755]
repository/flickr/PEAR/PEAR.php [new file with mode: 0755]
repository/flickr/phpFlickr.php [new file with mode: 0755]
repository/flickr/repository.class.php [new file with mode: 0755]
repository/picker.php
repository/style.css

index e88a5425fea9e8db82f7e202961fc99a268ce199..1477dd697353af68035039ea75cd9aa8e9504acd 100755 (executable)
@@ -92,6 +92,7 @@ class repository_boxnet extends repository{
     }
 
     public function print_search(){
+        echo '<input type="text" disabled="true" name="Search" value="search terms..." size="40" class="right"/>';
         return false;
     }
 }
diff --git a/repository/flickr/PEAR/DB.php b/repository/flickr/PEAR/DB.php
new file mode 100755 (executable)
index 0000000..e2beeca
--- /dev/null
@@ -0,0 +1,1388 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Database independent query interface
+ *
+ * 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   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id$
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+
+// {{{ constants
+// {{{ error codes
+
+/**#@+
+ * One of PEAR DB's portable error codes.
+ * @see DB_common::errorCode(), DB::errorMessage()
+ *
+ * {@internal If you add an error code here, make sure you also add a textual
+ * version of it in DB::errorMessage().}}
+ */
+
+/**
+ * The code returned by many methods upon success
+ */
+define('DB_OK', 1);
+
+/**
+ * Unkown error
+ */
+define('DB_ERROR', -1);
+
+/**
+ * Syntax error
+ */
+define('DB_ERROR_SYNTAX', -2);
+
+/**
+ * Tried to insert a duplicate value into a primary or unique index
+ */
+define('DB_ERROR_CONSTRAINT', -3);
+
+/**
+ * An identifier in the query refers to a non-existant object
+ */
+define('DB_ERROR_NOT_FOUND', -4);
+
+/**
+ * Tried to create a duplicate object
+ */
+define('DB_ERROR_ALREADY_EXISTS', -5);
+
+/**
+ * The current driver does not support the action you attempted
+ */
+define('DB_ERROR_UNSUPPORTED', -6);
+
+/**
+ * The number of parameters does not match the number of placeholders
+ */
+define('DB_ERROR_MISMATCH', -7);
+
+/**
+ * A literal submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID', -8);
+
+/**
+ * The current DBMS does not support the action you attempted
+ */
+define('DB_ERROR_NOT_CAPABLE', -9);
+
+/**
+ * A literal submitted was too long so the end of it was removed
+ */
+define('DB_ERROR_TRUNCATED', -10);
+
+/**
+ * A literal number submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_NUMBER', -11);
+
+/**
+ * A literal date submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_DATE', -12);
+
+/**
+ * Attempt to divide something by zero
+ */
+define('DB_ERROR_DIVZERO', -13);
+
+/**
+ * A database needs to be selected
+ */
+define('DB_ERROR_NODBSELECTED', -14);
+
+/**
+ * Could not create the object requested
+ */
+define('DB_ERROR_CANNOT_CREATE', -15);
+
+/**
+ * Could not drop the database requested because it does not exist
+ */
+define('DB_ERROR_CANNOT_DROP', -17);
+
+/**
+ * An identifier in the query refers to a non-existant table
+ */
+define('DB_ERROR_NOSUCHTABLE', -18);
+
+/**
+ * An identifier in the query refers to a non-existant column
+ */
+define('DB_ERROR_NOSUCHFIELD', -19);
+
+/**
+ * The data submitted to the method was inappropriate
+ */
+define('DB_ERROR_NEED_MORE_DATA', -20);
+
+/**
+ * The attempt to lock the table failed
+ */
+define('DB_ERROR_NOT_LOCKED', -21);
+
+/**
+ * The number of columns doesn't match the number of values
+ */
+define('DB_ERROR_VALUE_COUNT_ON_ROW', -22);
+
+/**
+ * The DSN submitted has problems
+ */
+define('DB_ERROR_INVALID_DSN', -23);
+
+/**
+ * Could not connect to the database
+ */
+define('DB_ERROR_CONNECT_FAILED', -24);
+
+/**
+ * The PHP extension needed for this DBMS could not be found
+ */
+define('DB_ERROR_EXTENSION_NOT_FOUND',-25);
+
+/**
+ * The present user has inadequate permissions to perform the task requestd
+ */
+define('DB_ERROR_ACCESS_VIOLATION', -26);
+
+/**
+ * The database requested does not exist
+ */
+define('DB_ERROR_NOSUCHDB', -27);
+
+/**
+ * Tried to insert a null value into a column that doesn't allow nulls
+ */
+define('DB_ERROR_CONSTRAINT_NOT_NULL',-29);
+/**#@-*/
+
+
+// }}}
+// {{{ prepared statement-related
+
+
+/**#@+
+ * Identifiers for the placeholders used in prepared statements.
+ * @see DB_common::prepare()
+ */
+
+/**
+ * Indicates a scalar (<kbd>?</kbd>) placeholder was used
+ *
+ * Quote and escape the value as necessary.
+ */
+define('DB_PARAM_SCALAR', 1);
+
+/**
+ * Indicates an opaque (<kbd>&</kbd>) placeholder was used
+ *
+ * The value presented is a file name.  Extract the contents of that file
+ * and place them in this column.
+ */
+define('DB_PARAM_OPAQUE', 2);
+
+/**
+ * Indicates a misc (<kbd>!</kbd>) placeholder was used
+ *
+ * The value should not be quoted or escaped.
+ */
+define('DB_PARAM_MISC',   3);
+/**#@-*/
+
+
+// }}}
+// {{{ binary data-related
+
+
+/**#@+
+ * The different ways of returning binary data from queries.
+ */
+
+/**
+ * Sends the fetched data straight through to output
+ */
+define('DB_BINMODE_PASSTHRU', 1);
+
+/**
+ * Lets you return data as usual
+ */
+define('DB_BINMODE_RETURN', 2);
+
+/**
+ * Converts the data to hex format before returning it
+ *
+ * For example the string "123" would become "313233".
+ */
+define('DB_BINMODE_CONVERT', 3);
+/**#@-*/
+
+
+// }}}
+// {{{ fetch modes
+
+
+/**#@+
+ * Fetch Modes.
+ * @see DB_common::setFetchMode()
+ */
+
+/**
+ * Indicates the current default fetch mode should be used
+ * @see DB_common::$fetchmode
+ */
+define('DB_FETCHMODE_DEFAULT', 0);
+
+/**
+ * Column data indexed by numbers, ordered from 0 and up
+ */
+define('DB_FETCHMODE_ORDERED', 1);
+
+/**
+ * Column data indexed by column names
+ */
+define('DB_FETCHMODE_ASSOC', 2);
+
+/**
+ * Column data as object properties
+ */
+define('DB_FETCHMODE_OBJECT', 3);
+
+/**
+ * For multi-dimensional results, make the column name the first level
+ * of the array and put the row number in the second level of the array
+ *
+ * This is flipped from the normal behavior, which puts the row numbers
+ * in the first level of the array and the column names in the second level.
+ */
+define('DB_FETCHMODE_FLIPPED', 4);
+/**#@-*/
+
+/**#@+
+ * Old fetch modes.  Left here for compatibility.
+ */
+define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
+define('DB_GETMODE_ASSOC',   DB_FETCHMODE_ASSOC);
+define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED);
+/**#@-*/
+
+
+// }}}
+// {{{ tableInfo() && autoPrepare()-related
+
+
+/**#@+
+ * The type of information to return from the tableInfo() method.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::tableInfo()
+ *
+ * {@internal Since the TABLEINFO constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}}
+ */
+define('DB_TABLEINFO_ORDER', 1);
+define('DB_TABLEINFO_ORDERTABLE', 2);
+define('DB_TABLEINFO_FULL', 3);
+/**#@-*/
+
+
+/**#@+
+ * The type of query to create with the automatic query building methods.
+ * @see DB_common::autoPrepare(), DB_common::autoExecute()
+ */
+define('DB_AUTOQUERY_INSERT', 1);
+define('DB_AUTOQUERY_UPDATE', 2);
+/**#@-*/
+
+
+// }}}
+// {{{ portability modes
+
+
+/**#@+
+ * Portability Modes.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::setOption()
+ *
+ * {@internal Since the PORTABILITY constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}}
+ */
+
+/**
+ * Turn off all portability features
+ */
+define('DB_PORTABILITY_NONE', 0);
+
+/**
+ * Convert names of tables and fields to lower case
+ * when using the get*(), fetch*() and tableInfo() methods
+ */
+define('DB_PORTABILITY_LOWERCASE', 1);
+
+/**
+ * Right trim the data output by get*() and fetch*()
+ */
+define('DB_PORTABILITY_RTRIM', 2);
+
+/**
+ * Force reporting the number of rows deleted
+ */
+define('DB_PORTABILITY_DELETE_COUNT', 4);
+
+/**
+ * Enable hack that makes numRows() work in Oracle
+ */
+define('DB_PORTABILITY_NUMROWS', 8);
+
+/**
+ * Makes certain error messages in certain drivers compatible
+ * with those from other DBMS's
+ *
+ * + mysql, mysqli:  change unique/primary key constraints
+ *   DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+ *
+ * + odbc(access):  MS's ODBC driver reports 'no such field' as code
+ *   07001, which means 'too few parameters.'  When this option is on
+ *   that code gets mapped to DB_ERROR_NOSUCHFIELD.
+ */
+define('DB_PORTABILITY_ERRORS', 16);
+
+/**
+ * Convert null values to empty strings in data output by
+ * get*() and fetch*()
+ */
+define('DB_PORTABILITY_NULL_TO_EMPTY', 32);
+
+/**
+ * Turn on all portability features
+ */
+define('DB_PORTABILITY_ALL', 63);
+/**#@-*/
+
+// }}}
+
+
+// }}}
+// {{{ class DB
+
+/**
+ * Database independent query interface
+ *
+ * The main "DB" class is simply a container class with some static
+ * methods for creating DB objects as well as some utility functions
+ * common to all parts of DB.
+ *
+ * The object model of DB is as follows (indentation means inheritance):
+ * <pre>
+ * DB           The main DB class.  This is simply a utility class
+ *              with some "static" methods for creating DB objects as
+ *              well as common utility functions for other DB classes.
+ *
+ * DB_common    The base for each DB implementation.  Provides default
+ * |            implementations (in OO lingo virtual methods) for
+ * |            the actual DB implementations as well as a bunch of
+ * |            query utility functions.
+ * |
+ * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
+ *              When calling DB::factory or DB::connect for MySQL
+ *              connections, the object returned is an instance of this
+ *              class.
+ * </pre>
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB
+{
+    // {{{ &factory()
+
+    /**
+     * Create a new DB object for the specified database type but don't
+     * connect to the database
+     *
+     * @param string $type     the database type (eg "mysql")
+     * @param array  $options  an associative array of option names and values
+     *
+     * @return object  a new DB object.  A DB_Error object on failure.
+     *
+     * @see DB_common::setOption()
+     */
+    function &factory($type, $options = false)
+    {
+        if (!is_array($options)) {
+            $options = array('persistent' => $options);
+        }
+
+        if (isset($options['debug']) && $options['debug'] >= 2) {
+            // expose php errors with sufficient debug level
+            include_once "DB/{$type}.php";
+        } else {
+            @include_once "DB/{$type}.php";
+        }
+
+        $classname = "DB_${type}";
+
+        if (!class_exists($classname)) {
+            $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+                                    "Unable to include the DB/{$type}.php"
+                                    . " file for '$dsn'",
+                                    'DB_Error', true);
+            return $tmp;
+        }
+
+        @$obj =& new $classname;
+
+        foreach ($options as $option => $value) {
+            $test = $obj->setOption($option, $value);
+            if (DB::isError($test)) {
+                return $test;
+            }
+        }
+
+        return $obj;
+    }
+
+    // }}}
+    // {{{ &connect()
+
+    /**
+     * Create a new DB object including a connection to the specified database
+     *
+     * Example 1.
+     * <code>
+     * require_once 'DB.php';
+     *
+     * $dsn = 'pgsql://user:password@host/database';
+     * $options = array(
+     *     'debug'       => 2,
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     *
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param mixed $dsn      the string "data source name" or array in the
+     *                         format returned by DB::parseDSN()
+     * @param array $options  an associative array of option names and values
+     *
+     * @return object  a new DB object.  A DB_Error object on failure.
+     *
+     * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(),
+     *       DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(),
+     *       DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(),
+     *       DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(),
+     *       DB_sybase::connect()
+     *
+     * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError()
+     */
+    function &connect($dsn, $options = array())
+    {
+        $dsninfo = DB::parseDSN($dsn);
+        $type = $dsninfo['phptype'];
+
+        if (!is_array($options)) {
+            /*
+             * For backwards compatibility.  $options used to be boolean,
+             * indicating whether the connection should be persistent.
+             */
+            $options = array('persistent' => $options);
+        }
+
+        if (isset($options['debug']) && $options['debug'] >= 2) {
+            // expose php errors with sufficient debug level
+            include_once "DB/${type}.php";
+        } else {
+            @include_once "DB/${type}.php";
+        }
+
+        $classname = "DB_${type}";
+        if (!class_exists($classname)) {
+            $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+                                    "Unable to include the DB/{$type}.php"
+                                    . " file for '$dsn'",
+                                    'DB_Error', true);
+            return $tmp;
+        }
+
+        @$obj =& new $classname;
+
+        foreach ($options as $option => $value) {
+            $test = $obj->setOption($option, $value);
+            if (DB::isError($test)) {
+                return $test;
+            }
+        }
+
+        $err = $obj->connect($dsninfo, $obj->getOption('persistent'));
+        if (DB::isError($err)) {
+            $err->addUserInfo($dsn);
+            return $err;
+        }
+
+        return $obj;
+    }
+
+    // }}}
+    // {{{ apiVersion()
+
+    /**
+     * Return the DB API version
+     *
+     * @return string  the DB API version number
+     */
+    function apiVersion()
+    {
+        return '@package_version@';
+    }
+
+    // }}}
+    // {{{ isError()
+
+    /**
+     * Determines if a variable is a DB_Error object
+     *
+     * @param mixed $value  the variable to check
+     *
+     * @return bool  whether $value is DB_Error object
+     */
+    function isError($value)
+    {
+        return is_a($value, 'DB_Error');
+    }
+
+    // }}}
+    // {{{ isConnection()
+
+    /**
+     * Determines if a value is a DB_<driver> object
+     *
+     * @param mixed $value  the value to test
+     *
+     * @return bool  whether $value is a DB_<driver> object
+     */
+    function isConnection($value)
+    {
+        return (is_object($value) &&
+                is_subclass_of($value, 'db_common') &&
+                method_exists($value, 'simpleQuery'));
+    }
+
+    // }}}
+    // {{{ isManip()
+
+    /**
+     * Tell whether a query is a data manipulation or data definition query
+     *
+     * Examples of data manipulation queries are INSERT, UPDATE and DELETE.
+     * Examples of data definition queries are CREATE, DROP, ALTER, GRANT,
+     * REVOKE.
+     *
+     * @param string $query  the query
+     *
+     * @return boolean  whether $query is a data manipulation query
+     */
+    function isManip($query)
+    {
+        $manips = 'INSERT|UPDATE|DELETE|REPLACE|'
+                . 'CREATE|DROP|'
+                . 'LOAD DATA|SELECT .* INTO|COPY|'
+                . 'ALTER|GRANT|REVOKE|'
+                . 'LOCK|UNLOCK';
+        if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) {
+            return true;
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ errorMessage()
+
+    /**
+     * Return a textual error message for a DB error code
+     *
+     * @param integer $value  the DB error code
+     *
+     * @return string  the error message or false if the error code was
+     *                  not recognized
+     */
+    function errorMessage($value)
+    {
+        static $errorMessages;
+        if (!isset($errorMessages)) {
+            $errorMessages = array(
+                DB_ERROR                    => 'unknown error',
+                DB_ERROR_ACCESS_VIOLATION   => 'insufficient permissions',
+                DB_ERROR_ALREADY_EXISTS     => 'already exists',
+                DB_ERROR_CANNOT_CREATE      => 'can not create',
+                DB_ERROR_CANNOT_DROP        => 'can not drop',
+                DB_ERROR_CONNECT_FAILED     => 'connect failed',
+                DB_ERROR_CONSTRAINT         => 'constraint violation',
+                DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+                DB_ERROR_DIVZERO            => 'division by zero',
+                DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
+                DB_ERROR_INVALID            => 'invalid',
+                DB_ERROR_INVALID_DATE       => 'invalid date or time',
+                DB_ERROR_INVALID_DSN        => 'invalid DSN',
+                DB_ERROR_INVALID_NUMBER     => 'invalid number',
+                DB_ERROR_MISMATCH           => 'mismatch',
+                DB_ERROR_NEED_MORE_DATA     => 'insufficient data supplied',
+                DB_ERROR_NODBSELECTED       => 'no database selected',
+                DB_ERROR_NOSUCHDB           => 'no such database',
+                DB_ERROR_NOSUCHFIELD        => 'no such field',
+                DB_ERROR_NOSUCHTABLE        => 'no such table',
+                DB_ERROR_NOT_CAPABLE        => 'DB backend not capable',
+                DB_ERROR_NOT_FOUND          => 'not found',
+                DB_ERROR_NOT_LOCKED         => 'not locked',
+                DB_ERROR_SYNTAX             => 'syntax error',
+                DB_ERROR_UNSUPPORTED        => 'not supported',
+                DB_ERROR_TRUNCATED          => 'truncated',
+                DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+                DB_OK                       => 'no error',
+            );
+        }
+
+        if (DB::isError($value)) {
+            $value = $value->getCode();
+        }
+
+        return isset($errorMessages[$value]) ? $errorMessages[$value]
+                     : $errorMessages[DB_ERROR];
+    }
+
+    // }}}
+    // {{{ parseDSN()
+
+    /**
+     * Parse a data source name
+     *
+     * Additional keys can be added by appending a URI query string to the
+     * end of the DSN.
+     *
+     * The format of the supplied DSN is in its fullest form:
+     * <code>
+     *  phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
+     * </code>
+     *
+     * Most variations are allowed:
+     * <code>
+     *  phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
+     *  phptype://username:password@hostspec/database_name
+     *  phptype://username:password@hostspec
+     *  phptype://username@hostspec
+     *  phptype://hostspec/database
+     *  phptype://hostspec
+     *  phptype(dbsyntax)
+     *  phptype
+     * </code>
+     *
+     * @param string $dsn Data Source Name to be parsed
+     *
+     * @return array an associative array with the following keys:
+     *  + phptype:  Database backend used in PHP (mysql, odbc etc.)
+     *  + dbsyntax: Database used with regards to SQL syntax etc.
+     *  + protocol: Communication protocol to use (tcp, unix etc.)
+     *  + hostspec: Host specification (hostname[:port])
+     *  + database: Database to use on the DBMS server
+     *  + username: User name for login
+     *  + password: Password for login
+     */
+    function parseDSN($dsn)
+    {
+        $parsed = array(
+            'phptype'  => false,
+            'dbsyntax' => false,
+            'username' => false,
+            'password' => false,
+            'protocol' => false,
+            'hostspec' => false,
+            'port'     => false,
+            'socket'   => false,
+            'database' => false,
+        );
+
+        if (is_array($dsn)) {
+            $dsn = array_merge($parsed, $dsn);
+            if (!$dsn['dbsyntax']) {
+                $dsn['dbsyntax'] = $dsn['phptype'];
+            }
+            return $dsn;
+        }
+
+        // Find phptype and dbsyntax
+        if (($pos = strpos($dsn, '://')) !== false) {
+            $str = substr($dsn, 0, $pos);
+            $dsn = substr($dsn, $pos + 3);
+        } else {
+            $str = $dsn;
+            $dsn = null;
+        }
+
+        // Get phptype and dbsyntax
+        // $str => phptype(dbsyntax)
+        if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+            $parsed['phptype']  = $arr[1];
+            $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
+        } else {
+            $parsed['phptype']  = $str;
+            $parsed['dbsyntax'] = $str;
+        }
+
+        if (!count($dsn)) {
+            return $parsed;
+        }
+
+        // Get (if found): username and password
+        // $dsn => username:password@protocol+hostspec/database
+        if (($at = strrpos($dsn,'@')) !== false) {
+            $str = substr($dsn, 0, $at);
+            $dsn = substr($dsn, $at + 1);
+            if (($pos = strpos($str, ':')) !== false) {
+                $parsed['username'] = rawurldecode(substr($str, 0, $pos));
+                $parsed['password'] = rawurldecode(substr($str, $pos + 1));
+            } else {
+                $parsed['username'] = rawurldecode($str);
+            }
+        }
+
+        // Find protocol and hostspec
+
+        if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
+            // $dsn => proto(proto_opts)/database
+            $proto       = $match[1];
+            $proto_opts  = $match[2] ? $match[2] : false;
+            $dsn         = $match[3];
+
+        } else {
+            // $dsn => protocol+hostspec/database (old format)
+            if (strpos($dsn, '+') !== false) {
+                list($proto, $dsn) = explode('+', $dsn, 2);
+            }
+            if (strpos($dsn, '/') !== false) {
+                list($proto_opts, $dsn) = explode('/', $dsn, 2);
+            } else {
+                $proto_opts = $dsn;
+                $dsn = null;
+            }
+        }
+
+        // process the different protocol options
+        $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
+        $proto_opts = rawurldecode($proto_opts);
+        if ($parsed['protocol'] == 'tcp') {
+            if (strpos($proto_opts, ':') !== false) {
+                list($parsed['hostspec'],
+                     $parsed['port']) = explode(':', $proto_opts);
+            } else {
+                $parsed['hostspec'] = $proto_opts;
+            }
+        } elseif ($parsed['protocol'] == 'unix') {
+            $parsed['socket'] = $proto_opts;
+        }
+
+        // Get dabase if any
+        // $dsn => database
+        if ($dsn) {
+            if (($pos = strpos($dsn, '?')) === false) {
+                // /database
+                $parsed['database'] = rawurldecode($dsn);
+            } else {
+                // /database?param1=value1&param2=value2
+                $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
+                $dsn = substr($dsn, $pos + 1);
+                if (strpos($dsn, '&') !== false) {
+                    $opts = explode('&', $dsn);
+                } else { // database?param1=value1
+                    $opts = array($dsn);
+                }
+                foreach ($opts as $opt) {
+                    list($key, $value) = explode('=', $opt);
+                    if (!isset($parsed[$key])) {
+                        // don't allow params overwrite
+                        $parsed[$key] = rawurldecode($value);
+                    }
+                }
+            }
+        }
+
+        return $parsed;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class DB_Error
+
+/**
+ * DB_Error implements a class for reporting portable database error
+ * messages
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_Error extends PEAR_Error
+{
+    // {{{ constructor
+
+    /**
+     * DB_Error constructor
+     *
+     * @param mixed $code       DB error code, or string with error message
+     * @param int   $mode       what "error mode" to operate in
+     * @param int   $level      what error level to use for $mode &
+     *                           PEAR_ERROR_TRIGGER
+     * @param mixed $debuginfo  additional debug info, such as the last query
+     *
+     * @see PEAR_Error
+     */
+    function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
+                      $level = E_USER_NOTICE, $debuginfo = null)
+    {
+        if (is_int($code)) {
+            $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code,
+                              $mode, $level, $debuginfo);
+        } else {
+            $this->PEAR_Error("DB Error: $code", DB_ERROR,
+                              $mode, $level, $debuginfo);
+        }
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class DB_result
+
+/**
+ * This class implements a wrapper for a DB result set
+ *
+ * A new instance of this class will be returned by the DB implementation
+ * after processing a query that returns data.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_result
+{
+    // {{{ properties
+
+    /**
+     * Should results be freed automatically when there are no more rows?
+     * @var boolean
+     * @see DB_common::$options
+     */
+    var $autofree;
+
+    /**
+     * A reference to the DB_<driver> object
+     * @var object
+     */
+    var $dbh;
+
+    /**
+     * The current default fetch mode
+     * @var integer
+     * @see DB_common::$fetchmode
+     */
+    var $fetchmode;
+
+    /**
+     * The name of the class into which results should be fetched when
+     * DB_FETCHMODE_OBJECT is in effect
+     *
+     * @var string
+     * @see DB_common::$fetchmode_object_class
+     */
+    var $fetchmode_object_class;
+
+    /**
+     * The number of rows to fetch from a limit query
+     * @var integer
+     */
+    var $limit_count = null;
+
+    /**
+     * The row to start fetching from in limit queries
+     * @var integer
+     */
+    var $limit_from = null;
+
+    /**
+     * The execute parameters that created this result
+     * @var array
+     * @since Property available since Release 1.7.0
+     */
+    var $parameters;
+
+    /**
+     * The query string that created this result
+     *
+     * Copied here incase it changes in $dbh, which is referenced
+     *
+     * @var string
+     * @since Property available since Release 1.7.0
+     */
+    var $query;
+
+    /**
+     * The query result resource id created by PHP
+     * @var resource
+     */
+    var $result;
+
+    /**
+     * The present row being dealt with
+     * @var integer
+     */
+    var $row_counter = null;
+
+    /**
+     * The prepared statement resource id created by PHP in $dbh
+     *
+     * This resource is only available when the result set was created using
+     * a driver's native execute() method, not PEAR DB's emulated one.
+     *
+     * Copied here incase it changes in $dbh, which is referenced
+     *
+     * {@internal  Mainly here because the InterBase/Firebird API is only
+     * able to retrieve data from result sets if the statemnt handle is
+     * still in scope.}}
+     *
+     * @var resource
+     * @since Property available since Release 1.7.0
+     */
+    var $statement;
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor sets the object's properties
+     *
+     * @param object   &$dbh     the DB object reference
+     * @param resource $result   the result resource id
+     * @param array    $options  an associative array with result options
+     *
+     * @return void
+     */
+    function DB_result(&$dbh, $result, $options = array())
+    {
+        $this->autofree    = $dbh->options['autofree'];
+        $this->dbh         = &$dbh;
+        $this->fetchmode   = $dbh->fetchmode;
+        $this->fetchmode_object_class = $dbh->fetchmode_object_class;
+        $this->parameters  = $dbh->last_parameters;
+        $this->query       = $dbh->last_query;
+        $this->result      = $result;
+        $this->statement   = empty($dbh->last_stmt) ? null : $dbh->last_stmt;
+        foreach ($options as $key => $value) {
+            $this->setOption($key, $value);
+        }
+    }
+
+    /**
+     * Set options for the DB_result object
+     *
+     * @param string $key    the option to set
+     * @param mixed  $value  the value to set the option to
+     *
+     * @return void
+     */
+    function setOption($key, $value = null)
+    {
+        switch ($key) {
+            case 'limit_from':
+                $this->limit_from = $value;
+                break;
+            case 'limit_count':
+                $this->limit_count = $value;
+        }
+    }
+
+    // }}}
+    // {{{ fetchRow()
+
+    /**
+     * Fetch a row of data and return it by reference into an array
+     *
+     * The type of array returned can be controlled either by setting this
+     * method's <var>$fetchmode</var> parameter or by changing the default
+     * fetch mode setFetchMode() before calling this method.
+     *
+     * There are two options for standardizing the information returned
+     * from databases, ensuring their values are consistent when changing
+     * DBMS's.  These portability options can be turned on when creating a
+     * new DB object or by using setOption().
+     *
+     *   + <var>DB_PORTABILITY_LOWERCASE</var>
+     *     convert names of fields to lower case
+     *
+     *   + <var>DB_PORTABILITY_RTRIM</var>
+     *     right trim the data
+     *
+     * @param int $fetchmode  the constant indicating how to format the data
+     * @param int $rownum     the row number to fetch (index starts at 0)
+     *
+     * @return mixed  an array or object containing the row's data,
+     *                 NULL when the end of the result set is reached
+     *                 or a DB_Error object on failure.
+     *
+     * @see DB_common::setOption(), DB_common::setFetchMode()
+     */
+    function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->fetchmode;
+        }
+        if ($fetchmode === DB_FETCHMODE_OBJECT) {
+            $fetchmode = DB_FETCHMODE_ASSOC;
+            $object_class = $this->fetchmode_object_class;
+        }
+        if ($this->limit_from !== null) {
+            if ($this->row_counter === null) {
+                $this->row_counter = $this->limit_from;
+                // Skip rows
+                if ($this->dbh->features['limit'] === false) {
+                    $i = 0;
+                    while ($i++ < $this->limit_from) {
+                        $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+                    }
+                }
+            }
+            if ($this->row_counter >= ($this->limit_from + $this->limit_count))
+            {
+                if ($this->autofree) {
+                    $this->free();
+                }
+                $tmp = null;
+                return $tmp;
+            }
+            if ($this->dbh->features['limit'] === 'emulate') {
+                $rownum = $this->row_counter;
+            }
+            $this->row_counter++;
+        }
+        $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+        if ($res === DB_OK) {
+            if (isset($object_class)) {
+                // The default mode is specified in the
+                // DB_common::fetchmode_object_class property
+                if ($object_class == 'stdClass') {
+                    $arr = (object) $arr;
+                } else {
+                    $arr = &new $object_class($arr);
+                }
+            }
+            return $arr;
+        }
+        if ($res == null && $this->autofree) {
+            $this->free();
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Fetch a row of data into an array which is passed by reference
+     *
+     * The type of array returned can be controlled either by setting this
+     * method's <var>$fetchmode</var> parameter or by changing the default
+     * fetch mode setFetchMode() before calling this method.
+     *
+     * There are two options for standardizing the information returned
+     * from databases, ensuring their values are consistent when changing
+     * DBMS's.  These portability options can be turned on when creating a
+     * new DB object or by using setOption().
+     *
+     *   + <var>DB_PORTABILITY_LOWERCASE</var>
+     *     convert names of fields to lower case
+     *
+     *   + <var>DB_PORTABILITY_RTRIM</var>
+     *     right trim the data
+     *
+     * @param array &$arr       the variable where the data should be placed
+     * @param int   $fetchmode  the constant indicating how to format the data
+     * @param int   $rownum     the row number to fetch (index starts at 0)
+     *
+     * @return mixed  DB_OK if a row is processed, NULL when the end of the
+     *                 result set is reached or a DB_Error object on failure
+     *
+     * @see DB_common::setOption(), DB_common::setFetchMode()
+     */
+    function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->fetchmode;
+        }
+        if ($fetchmode === DB_FETCHMODE_OBJECT) {
+            $fetchmode = DB_FETCHMODE_ASSOC;
+            $object_class = $this->fetchmode_object_class;
+        }
+        if ($this->limit_from !== null) {
+            if ($this->row_counter === null) {
+                $this->row_counter = $this->limit_from;
+                // Skip rows
+                if ($this->dbh->features['limit'] === false) {
+                    $i = 0;
+                    while ($i++ < $this->limit_from) {
+                        $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+                    }
+                }
+            }
+            if ($this->row_counter >= (
+                    $this->limit_from + $this->limit_count))
+            {
+                if ($this->autofree) {
+                    $this->free();
+                }
+                return null;
+            }
+            if ($this->dbh->features['limit'] === 'emulate') {
+                $rownum = $this->row_counter;
+            }
+
+            $this->row_counter++;
+        }
+        $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+        if ($res === DB_OK) {
+            if (isset($object_class)) {
+                // default mode specified in the
+                // DB_common::fetchmode_object_class property
+                if ($object_class == 'stdClass') {
+                    $arr = (object) $arr;
+                } else {
+                    $arr = new $object_class($arr);
+                }
+            }
+            return DB_OK;
+        }
+        if ($res == null && $this->autofree) {
+            $this->free();
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Get the the number of columns in a result set
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     */
+    function numCols()
+    {
+        return $this->dbh->numCols($this->result);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Get the number of rows in a result set
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function numRows()
+    {
+        if ($this->dbh->features['numrows'] === 'emulate'
+            && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS)
+        {
+            if ($this->dbh->features['prepare']) {
+                $res = $this->dbh->query($this->query, $this->parameters);
+            } else {
+                $res = $this->dbh->query($this->query);
+            }
+            if (DB::isError($res)) {
+                return $res;
+            }
+            $i = 0;
+            while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) {
+                $i++;
+            }
+            return $i;
+        } else {
+            return $this->dbh->numRows($this->result);
+        }
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Get the next result if a batch of queries was executed
+     *
+     * @return bool  true if a new result is available or false if not
+     */
+    function nextResult()
+    {
+        return $this->dbh->nextResult($this->result);
+    }
+
+    // }}}
+    // {{{ free()
+
+    /**
+     * Frees the resources allocated for this result set
+     *
+     * @return bool  true on success.  A DB_Error object on failure.
+     */
+    function free()
+    {
+        $err = $this->dbh->freeResult($this->result);
+        if (DB::isError($err)) {
+            return $err;
+        }
+        $this->result = false;
+        $this->statement = false;
+        return true;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * @see DB_common::tableInfo()
+     * @deprecated Method deprecated some time before Release 1.2
+     */
+    function tableInfo($mode = null)
+    {
+        if (is_string($mode)) {
+            return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+        return $this->dbh->tableInfo($this, $mode);
+    }
+
+    // }}}
+    // {{{ getQuery()
+
+    /**
+     * Determine the query string that created this result
+     *
+     * @return string  the query string
+     *
+     * @since Method available since Release 1.7.0
+     */
+    function getQuery()
+    {
+        return $this->query;
+    }
+
+    // }}}
+    // {{{ getRowCounter()
+
+    /**
+     * Tells which row number is currently being processed
+     *
+     * @return integer  the current row being looked at.  Starts at 1.
+     */
+    function getRowCounter()
+    {
+        return $this->row_counter;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class DB_row
+
+/**
+ * PEAR DB Row Object
+ *
+ * The object contains a row of data from a result set.  Each column's data
+ * is placed in a property named for the column.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ * @see        DB_common::setFetchMode()
+ */
+class DB_row
+{
+    // {{{ constructor
+
+    /**
+     * The constructor places a row's data into properties of this object
+     *
+     * @param array  the array containing the row's data
+     *
+     * @return void
+     */
+    function DB_row(&$arr)
+    {
+        foreach ($arr as $key => $value) {
+            $this->$key = &$arr[$key];
+        }
+    }
+
+    // }}}
+}
+
+// }}}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/repository/flickr/PEAR/DB/common.php b/repository/flickr/PEAR/DB/common.php
new file mode 100755 (executable)
index 0000000..7988e71
--- /dev/null
@@ -0,0 +1,2157 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Contains the DB_common base 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   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id$
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+/**
+ * DB_common is the base class from which each database driver class extends
+ *
+ * All common methods are declared here.  If a given DBMS driver contains
+ * a particular method, that method will overload the one here.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_common extends PEAR
+{
+    // {{{ properties
+
+    /**
+     * The current default fetch mode
+     * @var integer
+     */
+    var $fetchmode = DB_FETCHMODE_ORDERED;
+
+    /**
+     * The name of the class into which results should be fetched when
+     * DB_FETCHMODE_OBJECT is in effect
+     *
+     * @var string
+     */
+    var $fetchmode_object_class = 'stdClass';
+
+    /**
+     * Was a connection present when the object was serialized()?
+     * @var bool
+     * @see DB_common::__sleep(), DB_common::__wake()
+     */
+    var $was_connected = null;
+
+    /**
+     * The most recently executed query
+     * @var string
+     */
+    var $last_query = '';
+
+    /**
+     * Run-time configuration options
+     *
+     * The 'optimize' option has been deprecated.  Use the 'portability'
+     * option instead.
+     *
+     * @var array
+     * @see DB_common::setOption()
+     */
+    var $options = array(
+        'result_buffering' => 500,
+        'persistent' => false,
+        'ssl' => false,
+        'debug' => 0,
+        'seqname_format' => '%s_seq',
+        'autofree' => false,
+        'portability' => DB_PORTABILITY_NONE,
+        'optimize' => 'performance',  // Deprecated.  Use 'portability'.
+    );
+
+    /**
+     * The parameters from the most recently executed query
+     * @var array
+     * @since Property available since Release 1.7.0
+     */
+    var $last_parameters = array();
+
+    /**
+     * The elements from each prepared statement
+     * @var array
+     */
+    var $prepare_tokens = array();
+
+    /**
+     * The data types of the various elements in each prepared statement
+     * @var array
+     */
+    var $prepare_types = array();
+
+    /**
+     * The prepared queries
+     * @var array
+     */
+    var $prepared_queries = array();
+
+
+    // }}}
+    // {{{ DB_common
+
+    /**
+     * This constructor calls <kbd>$this->PEAR('DB_Error')</kbd>
+     *
+     * @return void
+     */
+    function DB_common()
+    {
+        $this->PEAR('DB_Error');
+    }
+
+    // }}}
+    // {{{ __sleep()
+
+    /**
+     * Automatically indicates which properties should be saved
+     * when PHP's serialize() function is called
+     *
+     * @return array  the array of properties names that should be saved
+     */
+    function __sleep()
+    {
+        if ($this->connection) {
+            // Don't disconnect(), people use serialize() for many reasons
+            $this->was_connected = true;
+        } else {
+            $this->was_connected = false;
+        }
+        if (isset($this->autocommit)) {
+            return array('autocommit',
+                         'dbsyntax',
+                         'dsn',
+                         'features',
+                         'fetchmode',
+                         'fetchmode_object_class',
+                         'options',
+                         'was_connected',
+                   );
+        } else {
+            return array('dbsyntax',
+                         'dsn',
+                         'features',
+                         'fetchmode',
+                         'fetchmode_object_class',
+                         'options',
+                         'was_connected',
+                   );
+        }
+    }
+
+    // }}}
+    // {{{ __wakeup()
+
+    /**
+     * Automatically reconnects to the database when PHP's unserialize()
+     * function is called
+     *
+     * The reconnection attempt is only performed if the object was connected
+     * at the time PHP's serialize() function was run.
+     *
+     * @return void
+     */
+    function __wakeup()
+    {
+        if ($this->was_connected) {
+            $this->connect($this->dsn, $this->options);
+        }
+    }
+
+    // }}}
+    // {{{ __toString()
+
+    /**
+     * Automatic string conversion for PHP 5
+     *
+     * @return string  a string describing the current PEAR DB object
+     *
+     * @since Method available since Release 1.7.0
+     */
+    function __toString()
+    {
+        $info = strtolower(get_class($this));
+        $info .=  ': (phptype=' . $this->phptype .
+                  ', dbsyntax=' . $this->dbsyntax .
+                  ')';
+        if ($this->connection) {
+            $info .= ' [connected]';
+        }
+        return $info;
+    }
+
+    // }}}
+    // {{{ toString()
+
+    /**
+     * DEPRECATED:  String conversion method
+     *
+     * @return string  a string describing the current PEAR DB object
+     *
+     * @deprecated Method deprecated in Release 1.7.0
+     */
+    function toString()
+    {
+        return $this->__toString();
+    }
+
+    // }}}
+    // {{{ quoteString()
+
+    /**
+     * DEPRECATED: Quotes a string so it can be safely used within string
+     * delimiters in a query
+     *
+     * @param string $string  the string to be quoted
+     *
+     * @return string  the quoted string
+     *
+     * @see DB_common::quoteSmart(), DB_common::escapeSimple()
+     * @deprecated Method deprecated some time before Release 1.2
+     */
+    function quoteString($string)
+    {
+        $string = $this->quote($string);
+        if ($string{0} == "'") {
+            return substr($string, 1, -1);
+        }
+        return $string;
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * DEPRECATED: Quotes a string so it can be safely used in a query
+     *
+     * @param string $string  the string to quote
+     *
+     * @return string  the quoted string or the string <samp>NULL</samp>
+     *                  if the value submitted is <kbd>null</kbd>.
+     *
+     * @see DB_common::quoteSmart(), DB_common::escapeSimple()
+     * @deprecated Deprecated in release 1.6.0
+     */
+    function quote($string = null)
+    {
+        return ($string === null) ? 'NULL'
+                                  : "'" . str_replace("'", "''", $string) . "'";
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * Delimiting style depends on which database driver is being used.
+     *
+     * NOTE: just because you CAN use delimited identifiers doesn't mean
+     * you SHOULD use them.  In general, they end up causing way more
+     * problems than they solve.
+     *
+     * Portability is broken by using the following characters inside
+     * delimited identifiers:
+     *   + backtick (<kbd>`</kbd>) -- due to MySQL
+     *   + double quote (<kbd>"</kbd>) -- due to Oracle
+     *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
+     *
+     * Delimited identifiers are known to generally work correctly under
+     * the following drivers:
+     *   + mssql
+     *   + mysql
+     *   + mysqli
+     *   + oci8
+     *   + odbc(access)
+     *   + odbc(db2)
+     *   + pgsql
+     *   + sqlite
+     *   + sybase (must execute <kbd>set quoted_identifier on</kbd> sometime
+     *     prior to use)
+     *
+     * InterBase doesn't seem to be able to use delimited identifiers
+     * via PHP 4.  They work fine under PHP 5.
+     *
+     * @param string $str  the identifier name to be quoted
+     *
+     * @return string  the quoted identifier
+     *
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        return '"' . str_replace('"', '""', $str) . '"';
+    }
+
+    // }}}
+    // {{{ quoteSmart()
+
+    /**
+     * Formats input so it can be safely used in a query
+     *
+     * The output depends on the PHP data type of input and the database
+     * type being used.
+     *
+     * @param mixed $in  the data to be formatted
+     *
+     * @return mixed  the formatted data.  The format depends on the input's
+     *                 PHP type:
+     * <ul>
+     *  <li>
+     *    <kbd>input</kbd> -> <samp>returns</samp>
+     *  </li>
+     *  <li>
+     *    <kbd>null</kbd> -> the string <samp>NULL</samp>
+     *  </li>
+     *  <li>
+     *    <kbd>integer</kbd> or <kbd>double</kbd> -> the unquoted number
+     *  </li>
+     *  <li>
+     *    <kbd>bool</kbd> -> output depends on the driver in use
+     *    Most drivers return integers: <samp>1</samp> if
+     *    <kbd>true</kbd> or <samp>0</samp> if
+     *    <kbd>false</kbd>.
+     *    Some return strings: <samp>TRUE</samp> if
+     *    <kbd>true</kbd> or <samp>FALSE</samp> if
+     *    <kbd>false</kbd>.
+     *    Finally one returns strings: <samp>T</samp> if
+     *    <kbd>true</kbd> or <samp>F</samp> if
+     *    <kbd>false</kbd>. Here is a list of each DBMS,
+     *    the values returned and the suggested column type:
+     *    <ul>
+     *      <li>
+     *        <kbd>dbase</kbd> -> <samp>T/F</samp>
+     *        (<kbd>Logical</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>fbase</kbd> -> <samp>TRUE/FALSE</samp>
+     *        (<kbd>BOOLEAN</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>ibase</kbd> -> <samp>1/0</samp>
+     *        (<kbd>SMALLINT</kbd>) [1]
+     *      </li>
+     *      <li>
+     *        <kbd>ifx</kbd> -> <samp>1/0</samp>
+     *        (<kbd>SMALLINT</kbd>) [1]
+     *      </li>
+     *      <li>
+     *        <kbd>msql</kbd> -> <samp>1/0</samp>
+     *        (<kbd>INTEGER</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>mssql</kbd> -> <samp>1/0</samp>
+     *        (<kbd>BIT</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>mysql</kbd> -> <samp>1/0</samp>
+     *        (<kbd>TINYINT(1)</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>mysqli</kbd> -> <samp>1/0</samp>
+     *        (<kbd>TINYINT(1)</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>oci8</kbd> -> <samp>1/0</samp>
+     *        (<kbd>NUMBER(1)</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>odbc</kbd> -> <samp>1/0</samp>
+     *        (<kbd>SMALLINT</kbd>) [1]
+     *      </li>
+     *      <li>
+     *        <kbd>pgsql</kbd> -> <samp>TRUE/FALSE</samp>
+     *        (<kbd>BOOLEAN</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>sqlite</kbd> -> <samp>1/0</samp>
+     *        (<kbd>INTEGER</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>sybase</kbd> -> <samp>1/0</samp>
+     *        (<kbd>TINYINT(1)</kbd>)
+     *      </li>
+     *    </ul>
+     *    [1] Accommodate the lowest common denominator because not all
+     *    versions of have <kbd>BOOLEAN</kbd>.
+     *  </li>
+     *  <li>
+     *    other (including strings and numeric strings) ->
+     *    the data with single quotes escaped by preceeding
+     *    single quotes, backslashes are escaped by preceeding
+     *    backslashes, then the whole string is encapsulated
+     *    between single quotes
+     *  </li>
+     * </ul>
+     *
+     * @see DB_common::escapeSimple()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteSmart($in)
+    {
+        if (is_int($in) || is_double($in)) {
+            return $in;
+        } elseif (is_bool($in)) {
+            return $in ? 1 : 0;
+        } elseif (is_null($in)) {
+            return 'NULL';
+        } else {
+            return "'" . $this->escapeSimple($in) . "'";
+        }
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * In SQLite, this makes things safe for inserts/updates, but may
+     * cause problems when performing text comparisons against columns
+     * containing binary data. See the
+     * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        return str_replace("'", "''", $str);
+    }
+
+    // }}}
+    // {{{ provides()
+
+    /**
+     * Tells whether the present driver supports a given feature
+     *
+     * @param string $feature  the feature you're curious about
+     *
+     * @return bool  whether this driver supports $feature
+     */
+    function provides($feature)
+    {
+        return $this->features[$feature];
+    }
+
+    // }}}
+    // {{{ setFetchMode()
+
+    /**
+     * Sets the fetch mode that should be used by default for query results
+     *
+     * @param integer $fetchmode    DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC
+     *                               or DB_FETCHMODE_OBJECT
+     * @param string $object_class  the class name of the object to be returned
+     *                               by the fetch methods when the
+     *                               DB_FETCHMODE_OBJECT mode is selected.
+     *                               If no class is specified by default a cast
+     *                               to object from the assoc array row will be
+     *                               done.  There is also the posibility to use
+     *                               and extend the 'DB_row' class.
+     *
+     * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT
+     */
+    function setFetchMode($fetchmode, $object_class = 'stdClass')
+    {
+        switch ($fetchmode) {
+            case DB_FETCHMODE_OBJECT:
+                $this->fetchmode_object_class = $object_class;
+            case DB_FETCHMODE_ORDERED:
+            case DB_FETCHMODE_ASSOC:
+                $this->fetchmode = $fetchmode;
+                break;
+            default:
+                return $this->raiseError('invalid fetchmode mode');
+        }
+    }
+
+    // }}}
+    // {{{ setOption()
+
+    /**
+     * Sets run-time configuration options for PEAR DB
+     *
+     * Options, their data types, default values and description:
+     * <ul>
+     * <li>
+     * <var>autofree</var> <kbd>boolean</kbd> = <samp>false</samp>
+     *      <br />should results be freed automatically when there are no
+     *            more rows?
+     * </li><li>
+     * <var>result_buffering</var> <kbd>integer</kbd> = <samp>500</samp>
+     *      <br />how many rows of the result set should be buffered?
+     *      <br />In mysql: mysql_unbuffered_query() is used instead of
+     *            mysql_query() if this value is 0.  (Release 1.7.0)
+     *      <br />In oci8: this value is passed to ocisetprefetch().
+     *            (Release 1.7.0)
+     * </li><li>
+     * <var>debug</var> <kbd>integer</kbd> = <samp>0</samp>
+     *      <br />debug level
+     * </li><li>
+     * <var>persistent</var> <kbd>boolean</kbd> = <samp>false</samp>
+     *      <br />should the connection be persistent?
+     * </li><li>
+     * <var>portability</var> <kbd>integer</kbd> = <samp>DB_PORTABILITY_NONE</samp>
+     *      <br />portability mode constant (see below)
+     * </li><li>
+     * <var>seqname_format</var> <kbd>string</kbd> = <samp>%s_seq</samp>
+     *      <br />the sprintf() format string used on sequence names.  This
+     *            format is applied to sequence names passed to
+     *            createSequence(), nextID() and dropSequence().
+     * </li><li>
+     * <var>ssl</var> <kbd>boolean</kbd> = <samp>false</samp>
+     *      <br />use ssl to connect?
+     * </li>
+     * </ul>
+     *
+     * -----------------------------------------
+     *
+     * PORTABILITY MODES
+     *
+     * These modes are bitwised, so they can be combined using <kbd>|</kbd>
+     * and removed using <kbd>^</kbd>.  See the examples section below on how
+     * to do this.
+     *
+     * <samp>DB_PORTABILITY_NONE</samp>
+     * turn off all portability features
+     *
+     * This mode gets automatically turned on if the deprecated
+     * <var>optimize</var> option gets set to <samp>performance</samp>.
+     *
+     *
+     * <samp>DB_PORTABILITY_LOWERCASE</samp>
+     * convert names of tables and fields to lower case when using
+     * <kbd>get*()</kbd>, <kbd>fetch*()</kbd> and <kbd>tableInfo()</kbd>
+     *
+     * This mode gets automatically turned on in the following databases
+     * if the deprecated option <var>optimize</var> gets set to
+     * <samp>portability</samp>:
+     * + oci8
+     *
+     *
+     * <samp>DB_PORTABILITY_RTRIM</samp>
+     * right trim the data output by <kbd>get*()</kbd> <kbd>fetch*()</kbd>
+     *
+     *
+     * <samp>DB_PORTABILITY_DELETE_COUNT</samp>
+     * force reporting the number of rows deleted
+     *
+     * Some DBMS's don't count the number of rows deleted when performing
+     * simple <kbd>DELETE FROM tablename</kbd> queries.  This portability
+     * mode tricks such DBMS's into telling the count by adding
+     * <samp>WHERE 1=1</samp> to the end of <kbd>DELETE</kbd> queries.
+     *
+     * This mode gets automatically turned on in the following databases
+     * if the deprecated option <var>optimize</var> gets set to
+     * <samp>portability</samp>:
+     * + fbsql
+     * + mysql
+     * + mysqli
+     * + sqlite
+     *
+     *
+     * <samp>DB_PORTABILITY_NUMROWS</samp>
+     * enable hack that makes <kbd>numRows()</kbd> work in Oracle
+     *
+     * This mode gets automatically turned on in the following databases
+     * if the deprecated option <var>optimize</var> gets set to
+     * <samp>portability</samp>:
+     * + oci8
+     *
+     *
+     * <samp>DB_PORTABILITY_ERRORS</samp>
+     * makes certain error messages in certain drivers compatible
+     * with those from other DBMS's
+     *
+     * + mysql, mysqli:  change unique/primary key constraints
+     *   DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+     *
+     * + odbc(access):  MS's ODBC driver reports 'no such field' as code
+     *   07001, which means 'too few parameters.'  When this option is on
+     *   that code gets mapped to DB_ERROR_NOSUCHFIELD.
+     *   DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD
+     *
+     * <samp>DB_PORTABILITY_NULL_TO_EMPTY</samp>
+     * convert null values to empty strings in data output by get*() and
+     * fetch*().  Needed because Oracle considers empty strings to be null,
+     * while most other DBMS's know the difference between empty and null.
+     *
+     *
+     * <samp>DB_PORTABILITY_ALL</samp>
+     * turn on all portability features
+     *
+     * -----------------------------------------
+     *
+     * Example 1. Simple setOption() example
+     * <code>
+     * $db->setOption('autofree', true);
+     * </code>
+     *
+     * Example 2. Portability for lowercasing and trimming
+     * <code>
+     * $db->setOption('portability',
+     *                 DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM);
+     * </code>
+     *
+     * Example 3. All portability options except trimming
+     * <code>
+     * $db->setOption('portability',
+     *                 DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM);
+     * </code>
+     *
+     * @param string $option option name
+     * @param mixed  $value value for the option
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::$options
+     */
+    function setOption($option, $value)
+    {
+        if (isset($this->options[$option])) {
+            $this->options[$option] = $value;
+
+            /*
+             * Backwards compatibility check for the deprecated 'optimize'
+             * option.  Done here in case settings change after connecting.
+             */
+            if ($option == 'optimize') {
+                if ($value == 'portability') {
+                    switch ($this->phptype) {
+                        case 'oci8':
+                            $this->options['portability'] =
+                                    DB_PORTABILITY_LOWERCASE |
+                                    DB_PORTABILITY_NUMROWS;
+                            break;
+                        case 'fbsql':
+                        case 'mysql':
+                        case 'mysqli':
+                        case 'sqlite':
+                            $this->options['portability'] =
+                                    DB_PORTABILITY_DELETE_COUNT;
+                            break;
+                    }
+                } else {
+                    $this->options['portability'] = DB_PORTABILITY_NONE;
+                }
+            }
+
+            return DB_OK;
+        }
+        return $this->raiseError("unknown option $option");
+    }
+
+    // }}}
+    // {{{ getOption()
+
+    /**
+     * Returns the value of an option
+     *
+     * @param string $option  the option name you're curious about
+     *
+     * @return mixed  the option's value
+     */
+    function getOption($option)
+    {
+        if (isset($this->options[$option])) {
+            return $this->options[$option];
+        }
+        return $this->raiseError("unknown option $option");
+    }
+
+    // }}}
+    // {{{ prepare()
+
+    /**
+     * Prepares a query for multiple execution with execute()
+     *
+     * Creates a query that can be run multiple times.  Each time it is run,
+     * the placeholders, if any, will be replaced by the contents of
+     * execute()'s $data argument.
+     *
+     * Three types of placeholders can be used:
+     *   + <kbd>?</kbd>  scalar value (i.e. strings, integers).  The system
+     *                   will automatically quote and escape the data.
+     *   + <kbd>!</kbd>  value is inserted 'as is'
+     *   + <kbd>&</kbd>  requires a file name.  The file's contents get
+     *                   inserted into the query (i.e. saving binary
+     *                   data in a db)
+     *
+     * Example 1.
+     * <code>
+     * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
+     * $data = array(
+     *     "John's text",
+     *     "'it''s good'",
+     *     'filename.txt'
+     * );
+     * $res = $db->execute($sth, $data);
+     * </code>
+     *
+     * Use backslashes to escape placeholder characters if you don't want
+     * them to be interpreted as placeholders:
+     * <pre>
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * </pre>
+     *
+     * With some database backends, this is emulated.
+     *
+     * {@internal ibase and oci8 have their own prepare() methods.}}
+     *
+     * @param string $query  the query to be prepared
+     *
+     * @return mixed  DB statement resource on success. A DB_Error object
+     *                 on failure.
+     *
+     * @see DB_common::execute()
+     */
+    function prepare($query)
+    {
+        $tokens   = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+                               PREG_SPLIT_DELIM_CAPTURE);
+        $token     = 0;
+        $types     = array();
+        $newtokens = array();
+
+        foreach ($tokens as $val) {
+            switch ($val) {
+                case '?':
+                    $types[$token++] = DB_PARAM_SCALAR;
+                    break;
+                case '&':
+                    $types[$token++] = DB_PARAM_OPAQUE;
+                    break;
+                case '!':
+                    $types[$token++] = DB_PARAM_MISC;
+                    break;
+                default:
+                    $newtokens[] = preg_replace('/\\\([&?!])/', "\\1", $val);
+            }
+        }
+
+        $this->prepare_tokens[] = &$newtokens;
+        end($this->prepare_tokens);
+
+        $k = key($this->prepare_tokens);
+        $this->prepare_types[$k] = $types;
+        $this->prepared_queries[$k] = implode(' ', $newtokens);
+
+        return $k;
+    }
+
+    // }}}
+    // {{{ autoPrepare()
+
+    /**
+     * Automaticaly generates an insert or update query and pass it to prepare()
+     *
+     * @param string $table         the table name
+     * @param array  $table_fields  the array of field names
+     * @param int    $mode          a type of query to make:
+     *                               DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+     * @param string $where         for update queries: the WHERE clause to
+     *                               append to the SQL statement.  Don't
+     *                               include the "WHERE" keyword.
+     *
+     * @return resource  the query handle
+     *
+     * @uses DB_common::prepare(), DB_common::buildManipSQL()
+     */
+    function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT,
+                         $where = false)
+    {
+        $query = $this->buildManipSQL($table, $table_fields, $mode, $where);
+        if (DB::isError($query)) {
+            return $query;
+        }
+        return $this->prepare($query);
+    }
+
+    // }}}
+    // {{{ autoExecute()
+
+    /**
+     * Automaticaly generates an insert or update query and call prepare()
+     * and execute() with it
+     *
+     * @param string $table         the table name
+     * @param array  $fields_values the associative array where $key is a
+     *                               field name and $value its value
+     * @param int    $mode          a type of query to make:
+     *                               DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+     * @param string $where         for update queries: the WHERE clause to
+     *                               append to the SQL statement.  Don't
+     *                               include the "WHERE" keyword.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     *
+     * @uses DB_common::autoPrepare(), DB_common::execute()
+     */
+    function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT,
+                         $where = false)
+    {
+        $sth = $this->autoPrepare($table, array_keys($fields_values), $mode,
+                                  $where);
+        if (DB::isError($sth)) {
+            return $sth;
+        }
+        $ret =& $this->execute($sth, array_values($fields_values));
+        $this->freePrepared($sth);
+        return $ret;
+
+    }
+
+    // }}}
+    // {{{ buildManipSQL()
+
+    /**
+     * Produces an SQL query string for autoPrepare()
+     *
+     * Example:
+     * <pre>
+     * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
+     *               DB_AUTOQUERY_INSERT);
+     * </pre>
+     *
+     * That returns
+     * <samp>
+     * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?)
+     * </samp>
+     *
+     * NOTES:
+     *   - This belongs more to a SQL Builder class, but this is a simple
+     *     facility.
+     *   - Be carefull! If you don't give a $where param with an UPDATE
+     *     query, all the records of the table will be updated!
+     *
+     * @param string $table         the table name
+     * @param array  $table_fields  the array of field names
+     * @param int    $mode          a type of query to make:
+     *                               DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+     * @param string $where         for update queries: the WHERE clause to
+     *                               append to the SQL statement.  Don't
+     *                               include the "WHERE" keyword.
+     *
+     * @return string  the sql query for autoPrepare()
+     */
+    function buildManipSQL($table, $table_fields, $mode, $where = false)
+    {
+        if (count($table_fields) == 0) {
+            return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+        $first = true;
+        switch ($mode) {
+            case DB_AUTOQUERY_INSERT:
+                $values = '';
+                $names = '';
+                foreach ($table_fields as $value) {
+                    if ($first) {
+                        $first = false;
+                    } else {
+                        $names .= ',';
+                        $values .= ',';
+                    }
+                    $names .= $value;
+                    $values .= '?';
+                }
+                return "INSERT INTO $table ($names) VALUES ($values)";
+            case DB_AUTOQUERY_UPDATE:
+                $set = '';
+                foreach ($table_fields as $value) {
+                    if ($first) {
+                        $first = false;
+                    } else {
+                        $set .= ',';
+                    }
+                    $set .= "$value = ?";
+                }
+                $sql = "UPDATE $table SET $set";
+                if ($where) {
+                    $sql .= " WHERE $where";
+                }
+                return $sql;
+            default:
+                return $this->raiseError(DB_ERROR_SYNTAX);
+        }
+    }
+
+    // }}}
+    // {{{ execute()
+
+    /**
+     * Executes a DB statement prepared with prepare()
+     *
+     * Example 1.
+     * <code>
+     * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
+     * $data = array(
+     *     "John's text",
+     *     "'it''s good'",
+     *     'filename.txt'
+     * );
+     * $res =& $db->execute($sth, $data);
+     * </code>
+     *
+     * @param resource $stmt  a DB statement resource returned from prepare()
+     * @param mixed    $data  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     *
+     * {@internal ibase and oci8 have their own execute() methods.}}
+     *
+     * @see DB_common::prepare()
+     */
+    function &execute($stmt, $data = array())
+    {
+        $realquery = $this->executeEmulateQuery($stmt, $data);
+        if (DB::isError($realquery)) {
+            return $realquery;
+        }
+        $result = $this->simpleQuery($realquery);
+
+        if ($result === DB_OK || DB::isError($result)) {
+            return $result;
+        } else {
+            $tmp =& new DB_result($this, $result);
+            return $tmp;
+        }
+    }
+
+    // }}}
+    // {{{ executeEmulateQuery()
+
+    /**
+     * Emulates executing prepared statements if the DBMS not support them
+     *
+     * @param resource $stmt  a DB statement resource returned from execute()
+     * @param mixed    $data  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a string containing the real query run when emulating
+     *                 prepare/execute.  A DB_Error object on failure.
+     *
+     * @access protected
+     * @see DB_common::execute()
+     */
+    function executeEmulateQuery($stmt, $data = array())
+    {
+        $stmt = (int)$stmt;
+        $data = (array)$data;
+        $this->last_parameters = $data;
+
+        if (count($this->prepare_types[$stmt]) != count($data)) {
+            $this->last_query = $this->prepared_queries[$stmt];
+            return $this->raiseError(DB_ERROR_MISMATCH);
+        }
+
+        $realquery = $this->prepare_tokens[$stmt][0];
+
+        $i = 0;
+        foreach ($data as $value) {
+            if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) {
+                $realquery .= $this->quoteSmart($value);
+            } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) {
+                $fp = @fopen($value, 'rb');
+                if (!$fp) {
+                    return $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+                }
+                $realquery .= $this->quoteSmart(fread($fp, filesize($value)));
+                fclose($fp);
+            } else {
+                $realquery .= $value;
+            }
+
+            $realquery .= $this->prepare_tokens[$stmt][++$i];
+        }
+
+        return $realquery;
+    }
+
+    // }}}
+    // {{{ executeMultiple()
+
+    /**
+     * Performs several execute() calls on the same statement handle
+     *
+     * $data must be an array indexed numerically
+     * from 0, one execute call is done for every "row" in the array.
+     *
+     * If an error occurs during execute(), executeMultiple() does not
+     * execute the unfinished rows, but rather returns that error.
+     *
+     * @param resource $stmt  query handle from prepare()
+     * @param array    $data  numeric array containing the
+     *                         data to insert into the query
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::prepare(), DB_common::execute()
+     */
+    function executeMultiple($stmt, $data)
+    {
+        foreach ($data as $value) {
+            $res =& $this->execute($stmt, $value);
+            if (DB::isError($res)) {
+                return $res;
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freePrepared()
+
+    /**
+     * Frees the internal resources associated with a prepared query
+     *
+     * @param resource $stmt           the prepared statement's PHP resource
+     * @param bool     $free_resource  should the PHP resource be freed too?
+     *                                  Use false if you need to get data
+     *                                  from the result set later.
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_common::prepare()
+     */
+    function freePrepared($stmt, $free_resource = true)
+    {
+        $stmt = (int)$stmt;
+        if (isset($this->prepare_tokens[$stmt])) {
+            unset($this->prepare_tokens[$stmt]);
+            unset($this->prepare_types[$stmt]);
+            unset($this->prepared_queries[$stmt]);
+            return true;
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * It is defined here to ensure all drivers have this method available.
+     *
+     * @param string $query  the query string to modify
+     *
+     * @return string  the modified query string
+     *
+     * @access protected
+     * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(),
+     *      DB_sqlite::modifyQuery()
+     */
+    function modifyQuery($query)
+    {
+        return $query;
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * It is defined here to assure that all implementations
+     * have this method defined.
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        return $query;
+    }
+
+    // }}}
+    // {{{ query()
+
+    /**
+     * Sends a query to the database server
+     *
+     * The query string can be either a normal statement to be sent directly
+     * to the server OR if <var>$params</var> are passed the query can have
+     * placeholders and it will be passed through prepare() and execute().
+     *
+     * @param string $query   the SQL query or the statement to prepare
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_result, DB_common::prepare(), DB_common::execute()
+     */
+    function &query($query, $params = array())
+    {
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+            $ret =& $this->execute($sth, $params);
+            $this->freePrepared($sth, false);
+            return $ret;
+        } else {
+            $this->last_parameters = array();
+            $result = $this->simpleQuery($query);
+            if ($result === DB_OK || DB::isError($result)) {
+                return $result;
+            } else {
+                $tmp =& new DB_result($this, $result);
+                return $tmp;
+            }
+        }
+    }
+
+    // }}}
+    // {{{ limitQuery()
+
+    /**
+     * Generates and executes a LIMIT query
+     *
+     * @param string $query   the query
+     * @param intr   $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     */
+    function &limitQuery($query, $from, $count, $params = array())
+    {
+        $query = $this->modifyLimitQuery($query, $from, $count, $params);
+        if (DB::isError($query)){
+            return $query;
+        }
+        $result =& $this->query($query, $params);
+        if (is_a($result, 'DB_result')) {
+            $result->setOption('limit_from', $from);
+            $result->setOption('limit_count', $count);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ getOne()
+
+    /**
+     * Fetches the first column of the first row from a query result
+     *
+     * Takes care of doing the query and freeing the results when finished.
+     *
+     * @param string $query   the SQL query
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  the returned value of the query.
+     *                 A DB_Error object on failure.
+     */
+    function &getOne($query, $params = array())
+    {
+        $params = (array)$params;
+        // modifyLimitQuery() would be nice here, but it causes BC issues
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+
+        $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED);
+        $res->free();
+
+        if ($err !== DB_OK) {
+            return $err;
+        }
+
+        return $row[0];
+    }
+
+    // }}}
+    // {{{ getRow()
+
+    /**
+     * Fetches the first row of data returned from a query result
+     *
+     * Takes care of doing the query and freeing the results when finished.
+     *
+     * @param string $query   the SQL query
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     * @param int $fetchmode  the fetch mode to use
+     *
+     * @return array  the first row of results as an array.
+     *                 A DB_Error object on failure.
+     */
+    function &getRow($query, $params = array(),
+                     $fetchmode = DB_FETCHMODE_DEFAULT)
+    {
+        // compat check, the params and fetchmode parameters used to
+        // have the opposite order
+        if (!is_array($params)) {
+            if (is_array($fetchmode)) {
+                if ($params === null) {
+                    $tmp = DB_FETCHMODE_DEFAULT;
+                } else {
+                    $tmp = $params;
+                }
+                $params = $fetchmode;
+                $fetchmode = $tmp;
+            } elseif ($params !== null) {
+                $fetchmode = $params;
+                $params = array();
+            }
+        }
+        // modifyLimitQuery() would be nice here, but it causes BC issues
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+
+        $err = $res->fetchInto($row, $fetchmode);
+
+        $res->free();
+
+        if ($err !== DB_OK) {
+            return $err;
+        }
+
+        return $row;
+    }
+
+    // }}}
+    // {{{ getCol()
+
+    /**
+     * Fetches a single column from a query result and returns it as an
+     * indexed array
+     *
+     * @param string $query   the SQL query
+     * @param mixed  $col     which column to return (integer [column number,
+     *                         starting at 0] or string [column name])
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return array  the results as an array.  A DB_Error object on failure.
+     *
+     * @see DB_common::query()
+     */
+    function &getCol($query, $col = 0, $params = array())
+    {
+        $params = (array)$params;
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+
+        $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC;
+
+        if (!is_array($row = $res->fetchRow($fetchmode))) {
+            $ret = array();
+        } else {
+            if (!array_key_exists($col, $row)) {
+                $ret =& $this->raiseError(DB_ERROR_NOSUCHFIELD);
+            } else {
+                $ret = array($row[$col]);
+                while (is_array($row = $res->fetchRow($fetchmode))) {
+                    $ret[] = $row[$col];
+                }
+            }
+        }
+
+        $res->free();
+
+        if (DB::isError($row)) {
+            $ret = $row;
+        }
+
+        return $ret;
+    }
+
+    // }}}
+    // {{{ getAssoc()
+
+    /**
+     * Fetches an entire query result and returns it as an
+     * associative array using the first column as the key
+     *
+     * If the result set contains more than two columns, the value
+     * will be an array of the values from column 2-n.  If the result
+     * set contains only two columns, the returned value will be a
+     * scalar with the value of the second column (unless forced to an
+     * array with the $force_array parameter).  A DB error code is
+     * returned on errors.  If the result set contains fewer than two
+     * columns, a DB_ERROR_TRUNCATED error is returned.
+     *
+     * For example, if the table "mytable" contains:
+     *
+     * <pre>
+     *  ID      TEXT       DATE
+     * --------------------------------
+     *  1       'one'      944679408
+     *  2       'two'      944679408
+     *  3       'three'    944679408
+     * </pre>
+     *
+     * Then the call getAssoc('SELECT id,text FROM mytable') returns:
+     * <pre>
+     *   array(
+     *     '1' => 'one',
+     *     '2' => 'two',
+     *     '3' => 'three',
+     *   )
+     * </pre>
+     *
+     * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns:
+     * <pre>
+     *   array(
+     *     '1' => array('one', '944679408'),
+     *     '2' => array('two', '944679408'),
+     *     '3' => array('three', '944679408')
+     *   )
+     * </pre>
+     *
+     * If the more than one row occurs with the same value in the
+     * first column, the last row overwrites all previous ones by
+     * default.  Use the $group parameter if you don't want to
+     * overwrite like this.  Example:
+     *
+     * <pre>
+     * getAssoc('SELECT category,id,name FROM mytable', false, null,
+     *          DB_FETCHMODE_ASSOC, true) returns:
+     *
+     *   array(
+     *     '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            ),
+     *     '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            )
+     *   )
+     * </pre>
+     *
+     * Keep in mind that database functions in PHP usually return string
+     * values for results regardless of the database's internal type.
+     *
+     * @param string $query        the SQL query
+     * @param bool   $force_array  used only when the query returns
+     *                              exactly two columns.  If true, the values
+     *                              of the returned array will be one-element
+     *                              arrays instead of scalars.
+     * @param mixed  $params       array, string or numeric data to be used in
+     *                              execution of the statement.  Quantity of
+     *                              items passed must match quantity of
+     *                              placeholders in query:  meaning 1
+     *                              placeholder for non-array parameters or
+     *                              1 placeholder per array element.
+     * @param int   $fetchmode     the fetch mode to use
+     * @param bool  $group         if true, the values of the returned array
+     *                              is wrapped in another array.  If the same
+     *                              key value (in the first column) repeats
+     *                              itself, the values will be appended to
+     *                              this array instead of overwriting the
+     *                              existing values.
+     *
+     * @return array  the associative array containing the query results.
+     *                A DB_Error object on failure.
+     */
+    function &getAssoc($query, $force_array = false, $params = array(),
+                       $fetchmode = DB_FETCHMODE_DEFAULT, $group = false)
+    {
+        $params = (array)$params;
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+        if ($fetchmode == DB_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->fetchmode;
+        }
+        $cols = $res->numCols();
+
+        if ($cols < 2) {
+            $tmp =& $this->raiseError(DB_ERROR_TRUNCATED);
+            return $tmp;
+        }
+
+        $results = array();
+
+        if ($cols > 2 || $force_array) {
+            // return array values
+            // XXX this part can be optimized
+            if ($fetchmode == DB_FETCHMODE_ASSOC) {
+                while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) {
+                    reset($row);
+                    $key = current($row);
+                    unset($row[key($row)]);
+                    if ($group) {
+                        $results[$key][] = $row;
+                    } else {
+                        $results[$key] = $row;
+                    }
+                }
+            } elseif ($fetchmode == DB_FETCHMODE_OBJECT) {
+                while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) {
+                    $arr = get_object_vars($row);
+                    $key = current($arr);
+                    if ($group) {
+                        $results[$key][] = $row;
+                    } else {
+                        $results[$key] = $row;
+                    }
+                }
+            } else {
+                while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
+                    // we shift away the first element to get
+                    // indices running from 0 again
+                    $key = array_shift($row);
+                    if ($group) {
+                        $results[$key][] = $row;
+                    } else {
+                        $results[$key] = $row;
+                    }
+                }
+            }
+            if (DB::isError($row)) {
+                $results = $row;
+            }
+        } else {
+            // return scalar values
+            while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
+                if ($group) {
+                    $results[$row[0]][] = $row[1];
+                } else {
+                    $results[$row[0]] = $row[1];
+                }
+            }
+            if (DB::isError($row)) {
+                $results = $row;
+            }
+        }
+
+        $res->free();
+
+        return $results;
+    }
+
+    // }}}
+    // {{{ getAll()
+
+    /**
+     * Fetches all of the rows from a query result
+     *
+     * @param string $query      the SQL query
+     * @param mixed  $params     array, string or numeric data to be used in
+     *                            execution of the statement.  Quantity of
+     *                            items passed must match quantity of
+     *                            placeholders in query:  meaning 1
+     *                            placeholder for non-array parameters or
+     *                            1 placeholder per array element.
+     * @param int    $fetchmode  the fetch mode to use:
+     *                            + DB_FETCHMODE_ORDERED
+     *                            + DB_FETCHMODE_ASSOC
+     *                            + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED
+     *                            + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED
+     *
+     * @return array  the nested array.  A DB_Error object on failure.
+     */
+    function &getAll($query, $params = array(),
+                     $fetchmode = DB_FETCHMODE_DEFAULT)
+    {
+        // compat check, the params and fetchmode parameters used to
+        // have the opposite order
+        if (!is_array($params)) {
+            if (is_array($fetchmode)) {
+                if ($params === null) {
+                    $tmp = DB_FETCHMODE_DEFAULT;
+                } else {
+                    $tmp = $params;
+                }
+                $params = $fetchmode;
+                $fetchmode = $tmp;
+            } elseif ($params !== null) {
+                $fetchmode = $params;
+                $params = array();
+            }
+        }
+
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if ($res === DB_OK || DB::isError($res)) {
+            return $res;
+        }
+
+        $results = array();
+        while (DB_OK === $res->fetchInto($row, $fetchmode)) {
+            if ($fetchmode & DB_FETCHMODE_FLIPPED) {
+                foreach ($row as $key => $val) {
+                    $results[$key][] = $val;
+                }
+            } else {
+                $results[] = $row;
+            }
+        }
+
+        $res->free();
+
+        if (DB::isError($row)) {
+            $tmp =& $this->raiseError($row);
+            return $tmp;
+        }
+        return $results;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Determines the number of rows in a query result
+     *
+     * @param resource $result  the query result idenifier produced by PHP
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function numRows($result)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ getSequenceName()
+
+    /**
+     * Generates the name used inside the database for a sequence
+     *
+     * The createSequence() docblock contains notes about storing sequence
+     * names.
+     *
+     * @param string $sqn  the sequence's public name
+     *
+     * @return string  the sequence's name in the backend
+     *
+     * @access protected
+     * @see DB_common::createSequence(), DB_common::dropSequence(),
+     *      DB_common::nextID(), DB_common::setOption()
+     */
+    function getSequenceName($sqn)
+    {
+        return sprintf($this->getOption('seqname_format'),
+                       preg_replace('/[^a-z0-9_.]/i', '_', $sqn));
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::dropSequence(),
+     *      DB_common::getSequenceName()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * The name of a given sequence is determined by passing the string
+     * provided in the <var>$seq_name</var> argument through PHP's sprintf()
+     * function using the value from the <var>seqname_format</var> option as
+     * the sprintf()'s format argument.
+     *
+     * <var>seqname_format</var> is set via setOption().
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_common::nextID()
+     */
+    function createSequence($seq_name)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_common::nextID()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ raiseError()
+
+    /**
+     * Communicates an error and invoke error callbacks, etc
+     *
+     * Basically a wrapper for PEAR::raiseError without the message string.
+     *
+     * @param mixed   integer error code, or a PEAR error object (all
+     *                 other parameters are ignored if this parameter is
+     *                 an object
+     * @param int     error mode, see PEAR_Error docs
+     * @param mixed   if error mode is PEAR_ERROR_TRIGGER, this is the
+     *                 error level (E_USER_NOTICE etc).  If error mode is
+     *                 PEAR_ERROR_CALLBACK, this is the callback function,
+     *                 either as a function name, or as an array of an
+     *                 object and method name.  For other error modes this
+     *                 parameter is ignored.
+     * @param string  extra debug information.  Defaults to the last
+     *                 query and native error code.
+     * @param mixed   native error code, integer or string depending the
+     *                 backend
+     *
+     * @return object  the PEAR_Error object
+     *
+     * @see PEAR_Error
+     */
+    function &raiseError($code = DB_ERROR, $mode = null, $options = null,
+                         $userinfo = null, $nativecode = null)
+    {
+        // The error is yet a DB error object
+        if (is_object($code)) {
+            // because we the static PEAR::raiseError, our global
+            // handler should be used if it is set
+            if ($mode === null && !empty($this->_default_error_mode)) {
+                $mode    = $this->_default_error_mode;
+                $options = $this->_default_error_options;
+            }
+            $tmp = PEAR::raiseError($code, null, $mode, $options,
+                                    null, null, true);
+            return $tmp;
+        }
+
+        if ($userinfo === null) {
+            $userinfo = $this->last_query;
+        }
+
+        if ($nativecode) {
+            $userinfo .= ' [nativecode=' . trim($nativecode) . ']';
+        } else {
+            $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']';
+        }
+
+        $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo,
+                                'DB_Error', true);
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return mixed  the DBMS' error code.  A DB_Error object on failure.
+     */
+    function errorNative()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Maps native error codes to DB's portable ones
+     *
+     * Uses the <var>$errorcode_map</var> property defined in each driver.
+     *
+     * @param string|int $nativecode  the error code returned by the DBMS
+     *
+     * @return int  the portable DB error code.  Return DB_ERROR if the
+     *               current driver doesn't have a mapping for the
+     *               $nativecode submitted.
+     */
+    function errorCode($nativecode)
+    {
+        if (isset($this->errorcode_map[$nativecode])) {
+            return $this->errorcode_map[$nativecode];
+        }
+        // Fall back to DB_ERROR if there was no mapping.
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ errorMessage()
+
+    /**
+     * Maps a DB error code to a textual message
+     *
+     * @param integer $dbcode  the DB error code
+     *
+     * @return string  the error message corresponding to the error code
+     *                  submitted.  FALSE if the error code is unknown.
+     *
+     * @see DB::errorMessage()
+     */
+    function errorMessage($dbcode)
+    {
+        return DB::errorMessage($this->errorcode_map[$dbcode]);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * The format of the resulting array depends on which <var>$mode</var>
+     * you select.  The sample output below is based on this query:
+     * <pre>
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * </pre>
+     *
+     * <ul>
+     * <li>
+     *
+     * <kbd>null</kbd> (default)
+     *   <pre>
+     *   [0] => Array (
+     *       [table] => tblFoo
+     *       [name] => fldId
+     *       [type] => int
+     *       [len] => 11
+     *       [flags] => primary_key not_null
+     *   )
+     *   [1] => Array (
+     *       [table] => tblFoo
+     *       [name] => fldPhone
+     *       [type] => string
+     *       [len] => 20
+     *       [flags] =>
+     *   )
+     *   [2] => Array (
+     *       [table] => tblBar
+     *       [name] => fldId
+     *       [type] => int
+     *       [len] => 11
+     *       [flags] => primary_key not_null
+     *   )
+     *   </pre>
+     *
+     * </li><li>
+     *
+     * <kbd>DB_TABLEINFO_ORDER</kbd>
+     *
+     *   <p>In addition to the information found in the default output,
+     *   a notation of the number of columns is provided by the
+     *   <samp>num_fields</samp> element while the <samp>order</samp>
+     *   element provides an array with the column names as the keys and
+     *   their location index number (corresponding to the keys in the
+     *   the default output) as the values.</p>
+     *
+     *   <p>If a result set has identical field names, the last one is
+     *   used.</p>
+     *
+     *   <pre>
+     *   [num_fields] => 3
+     *   [order] => Array (
+     *       [fldId] => 2
+     *       [fldTrans] => 1
+     *   )
+     *   </pre>
+     *
+     * </li><li>
+     *
+     * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>
+     *
+     *   <p>Similar to <kbd>DB_TABLEINFO_ORDER</kbd> but adds more
+     *   dimensions to the array in which the table names are keys and
+     *   the field names are sub-keys.  This is helpful for queries that
+     *   join tables which have identical field names.</p>
+     *
+     *   <pre>
+     *   [num_fields] => 3
+     *   [ordertable] => Array (
+     *       [tblFoo] => Array (
+     *           [fldId] => 0
+     *           [fldPhone] => 1
+     *       )
+     *       [tblBar] => Array (
+     *           [fldId] => 2
+     *       )
+     *   )
+     *   </pre>
+     *
+     * </li>
+     * </ul>
+     *
+     * The <samp>flags</samp> element contains a space separated list
+     * of extra information about the field.  This data is inconsistent
+     * between DBMS's due to the way each DBMS works.
+     *   + <samp>primary_key</samp>
+     *   + <samp>unique_key</samp>
+     *   + <samp>multiple_key</samp>
+     *   + <samp>not_null</samp>
+     *
+     * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp>
+     * elements if <var>$result</var> is a table name.  The following DBMS's
+     * provide full information from queries:
+     *   + fbsql
+     *   + mysql
+     *
+     * If the 'portability' option has <samp>DB_PORTABILITY_LOWERCASE</samp>
+     * turned on, the names of tables and fields will be lowercased.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                string containing the name of a table.
+     *                                While this also accepts a query result
+     *                                resource identifier, this behavior is
+     *                                deprecated.
+     * @param int  $mode   either unused or one of the tableInfo modes:
+     *                     <kbd>DB_TABLEINFO_ORDERTABLE</kbd>,
+     *                     <kbd>DB_TABLEINFO_ORDER</kbd> or
+     *                     <kbd>DB_TABLEINFO_FULL</kbd> (which does both).
+     *                     These are bitwise, so the first two can be
+     *                     combined using <kbd>|</kbd>.
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        /*
+         * If the DB_<driver> class has a tableInfo() method, that one
+         * overrides this one.  But, if the driver doesn't have one,
+         * this method runs and tells users about that fact.
+         */
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ getTables()
+
+    /**
+     * Lists the tables in the current database
+     *
+     * @return array  the list of tables.  A DB_Error object on failure.
+     *
+     * @deprecated Method deprecated some time before Release 1.2
+     */
+    function getTables()
+    {
+        return $this->getListOf('tables');
+    }
+
+    // }}}
+    // {{{ getListOf()
+
+    /**
+     * Lists internal database information
+     *
+     * @param string $type  type of information being sought.
+     *                       Common items being sought are:
+     *                       tables, databases, users, views, functions
+     *                       Each DBMS's has its own capabilities.
+     *
+     * @return array  an array listing the items sought.
+     *                 A DB DB_Error object on failure.
+     */
+    function getListOf($type)
+    {
+        $sql = $this->getSpecialQuery($type);
+        if ($sql === null) {
+            $this->last_query = '';
+            return $this->raiseError(DB_ERROR_UNSUPPORTED);
+        } elseif (is_int($sql) || DB::isError($sql)) {
+            // Previous error
+            return $this->raiseError($sql);
+        } elseif (is_array($sql)) {
+            // Already the result
+            return $sql;
+        }
+        // Launch this query
+        return $this->getCol($sql);
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        return $this->raiseError(DB_ERROR_UNSUPPORTED);
+    }
+
+    // }}}
+    // {{{ _rtrimArrayValues()
+
+    /**
+     * Right-trims all strings in an array
+     *
+     * @param array $array  the array to be trimmed (passed by reference)
+     *
+     * @return void
+     *
+     * @access protected
+     */
+    function _rtrimArrayValues(&$array)
+    {
+        foreach ($array as $key => $value) {
+            if (is_string($value)) {
+                $array[$key] = rtrim($value);
+            }
+        }
+    }
+
+    // }}}
+    // {{{ _convertNullArrayValuesToEmpty()
+
+    /**
+     * Converts all null values in an array to empty strings
+     *
+     * @param array  $array  the array to be de-nullified (passed by reference)
+     *
+     * @return void
+     *
+     * @access protected
+     */
+    function _convertNullArrayValuesToEmpty(&$array)
+    {
+        foreach ($array as $key => $value) {
+            if (is_null($value)) {
+                $array[$key] = '';
+            }
+        }
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/repository/flickr/PEAR/DB/mysql.php b/repository/flickr/PEAR/DB/mysql.php
new file mode 100755 (executable)
index 0000000..5b737b6
--- /dev/null
@@ -0,0 +1,1034 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mysql extension
+ * for interacting with MySQL databases
+ *
+ * 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   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id$
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mysql extension
+ * for interacting with MySQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_mysql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'mysql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'mysql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => '4.2.0',
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        1004 => DB_ERROR_CANNOT_CREATE,
+        1005 => DB_ERROR_CANNOT_CREATE,
+        1006 => DB_ERROR_CANNOT_CREATE,
+        1007 => DB_ERROR_ALREADY_EXISTS,
+        1008 => DB_ERROR_CANNOT_DROP,
+        1022 => DB_ERROR_ALREADY_EXISTS,
+        1044 => DB_ERROR_ACCESS_VIOLATION,
+        1046 => DB_ERROR_NODBSELECTED,
+        1048 => DB_ERROR_CONSTRAINT,
+        1049 => DB_ERROR_NOSUCHDB,
+        1050 => DB_ERROR_ALREADY_EXISTS,
+        1051 => DB_ERROR_NOSUCHTABLE,
+        1054 => DB_ERROR_NOSUCHFIELD,
+        1061 => DB_ERROR_ALREADY_EXISTS,
+        1062 => DB_ERROR_ALREADY_EXISTS,
+        1064 => DB_ERROR_SYNTAX,
+        1091 => DB_ERROR_NOT_FOUND,
+        1100 => DB_ERROR_NOT_LOCKED,
+        1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
+        1142 => DB_ERROR_ACCESS_VIOLATION,
+        1146 => DB_ERROR_NOSUCHTABLE,
+        1216 => DB_ERROR_CONSTRAINT,
+        1217 => DB_ERROR_CONSTRAINT,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The database specified in the DSN
+     *
+     * It's a fix to allow calls to different databases in the same script.
+     *
+     * @var string
+     * @access private
+     */
+    var $_db = '';
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_mysql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's mysql driver supports the following extra DSN options:
+     *   + new_link      If set to true, causes subsequent calls to connect()
+     *                    to return a new connection link instead of the
+     *                    existing one.  WARNING: this is not portable to
+     *                    other DBMS's. Available since PEAR DB 1.7.0.
+     *   + client_flags  Any combination of MYSQL_CLIENT_* constants.
+     *                    Only used if PHP is at version 4.3.0 or greater.
+     *                    Available since PEAR DB 1.7.0.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('mysql')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $params = array();
+        if ($dsn['protocol'] && $dsn['protocol'] == 'unix') {
+            $params[0] = ':' . $dsn['socket'];
+        } else {
+            $params[0] = $dsn['hostspec'] ? $dsn['hostspec']
+                         : 'localhost';
+            if ($dsn['port']) {
+                $params[0] .= ':' . $dsn['port'];
+            }
+        }
+        $params[] = $dsn['username'] ? $dsn['username'] : null;
+        $params[] = $dsn['password'] ? $dsn['password'] : null;
+
+        if (!$persistent) {
+            if (isset($dsn['new_link'])
+                && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+            {
+                $params[] = true;
+            } else {
+                $params[] = false;
+            }
+        }
+        if (version_compare(phpversion(), '4.3.0', '>=')) {
+            $params[] = isset($dsn['client_flags'])
+                        ? $dsn['client_flags'] : null;
+        }
+
+        $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
+
+        $ini = ini_get('track_errors');
+        $php_errormsg = '';
+        if ($ini) {
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+        } else {
+            ini_set('track_errors', 1);
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+            ini_set('track_errors', $ini);
+        }
+
+        if (!$this->connection) {
+            if (($err = @mysql_error()) != '') {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null, 
+                                         $err);
+            } else {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+        }
+
+        if ($dsn['database']) {
+            if (!@mysql_select_db($dsn['database'], $this->connection)) {
+                return $this->mysqlRaiseError();
+            }
+            $this->_db = $dsn['database'];
+        }
+
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @mysql_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * Generally uses mysql_query().  If you want to use
+     * mysql_unbuffered_query() set the "result_buffering" option to 0 using
+     * setOptions().  This option was added in Release 1.7.0.
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        if ($this->_db) {
+            if (!@mysql_select_db($this->_db, $this->connection)) {
+                return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+            }
+        }
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection);
+                $result = @mysql_query('BEGIN', $this->connection);
+                if (!$result) {
+                    return $this->mysqlRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        if (!$this->options['result_buffering']) {
+            $result = @mysql_unbuffered_query($query, $this->connection);
+        } else {
+            $result = @mysql_query($query, $this->connection);
+        }
+        if (!$result) {
+            return $this->mysqlRaiseError();
+        }
+        if (is_resource($result)) {
+            return $result;
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal mysql result pointer to the next available result
+     *
+     * This method has not been implemented yet.
+     *
+     * @param a valid sql result resource
+     *
+     * @return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@mysql_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @mysql_fetch_array($result, MYSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @mysql_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            /*
+             * Even though this DBMS already trims output, we do this because
+             * a field might have intentional whitespace at the end that
+             * gets removed by DB_PORTABILITY_RTRIM under another driver.
+             */
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @mysql_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @mysql_num_fields($result);
+        if (!$cols) {
+            return $this->mysqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @mysql_num_rows($result);
+        if ($rows === null) {
+            return $this->mysqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            if ($this->_db) {
+                if (!@mysql_select_db($this->_db, $this->connection)) {
+                    return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+                }
+            }
+            $result = @mysql_query('COMMIT', $this->connection);
+            $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mysqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            if ($this->_db) {
+                if (!@mysql_select_db($this->_db, $this->connection)) {
+                    return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+                }
+            }
+            $result = @mysql_query('ROLLBACK', $this->connection);
+            $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mysqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            return @mysql_affected_rows($this->connection);
+        } else {
+            return 0;
+        }
+     }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_mysql::createSequence(), DB_mysql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        do {
+            $repeat = 0;
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query("UPDATE ${seqname} ".
+                                   'SET id=LAST_INSERT_ID(id+1)');
+            $this->popErrorHandling();
+            if ($result === DB_OK) {
+                // COMMON CASE
+                $id = @mysql_insert_id($this->connection);
+                if ($id != 0) {
+                    return $id;
+                }
+                // EMPTY SEQ TABLE
+                // Sequence table must be empty for some reason, so fill
+                // it and return 1 and obtain a user-level lock
+                $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                if ($result == 0) {
+                    // Failed to get the lock
+                    return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
+                }
+
+                // add the default value
+                $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+
+                // Release the lock
+                $result = $this->getOne('SELECT RELEASE_LOCK('
+                                        . "'${seqname}_lock')");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                // We know what the result will be, so no need to try again
+                return 1;
+
+            } elseif ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE)
+            {
+                // ONDEMAND TABLE CREATION
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                } else {
+                    $repeat = 1;
+                }
+
+            } elseif (DB::isError($result) &&
+                      $result->getCode() == DB_ERROR_ALREADY_EXISTS)
+            {
+                // BACKWARDS COMPAT
+                // see _BCsequence() comment
+                $result = $this->_BCsequence($seqname);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                $repeat = 1;
+            }
+        } while ($repeat);
+
+        return $this->raiseError($result);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_mysql::nextID(), DB_mysql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $res = $this->query('CREATE TABLE ' . $seqname
+                            . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'
+                            . ' PRIMARY KEY(id))');
+        if (DB::isError($res)) {
+            return $res;
+        }
+        // insert yields value 1, nextId call will generate ID 2
+        $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
+        if (DB::isError($res)) {
+            return $res;
+        }
+        // so reset to zero
+        return $this->query("UPDATE ${seqname} SET id = 0");
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_mysql::nextID(), DB_mysql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ _BCsequence()
+
+    /**
+     * Backwards compatibility with old sequence emulation implementation
+     * (clean up the dupes)
+     *
+     * @param string $seqname  the sequence name to clean up
+     *
+     * @return bool  true on success.  A DB_Error object on failure.
+     *
+     * @access private
+     */
+    function _BCsequence($seqname)
+    {
+        // Obtain a user-level lock... this will release any previous
+        // application locks, but unlike LOCK TABLES, it does not abort
+        // the current transaction and is much less frequently used.
+        $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+        if (DB::isError($result)) {
+            return $result;
+        }
+        if ($result == 0) {
+            // Failed to get the lock, can't do the conversion, bail
+            // with a DB_ERROR_NOT_LOCKED error
+            return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
+        }
+
+        $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
+        if (DB::isError($highest_id)) {
+            return $highest_id;
+        }
+        // This should kill all rows except the highest
+        // We should probably do something if $highest_id isn't
+        // numeric, but I'm at a loss as how to handle that...
+        $result = $this->query('DELETE FROM ' . $seqname
+                               . " WHERE id <> $highest_id");
+        if (DB::isError($result)) {
+            return $result;
+        }
+
+        // If another thread has been waiting for this lock,
+        // it will go thru the above procedure, but will have no
+        // real effect
+        $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
+        if (DB::isError($result)) {
+            return $result;
+        }
+        return true;
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * MySQL can't handle the backtick character (<kbd>`</kbd>) in
+     * table or column names.
+     *
+     * @param string $str  identifier name to be quoted
+     *
+     * @return string  quoted identifier string
+     *
+     * @see DB_common::quoteIdentifier()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        return '`' . $str . '`';
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * @deprecated  Deprecated in release 1.6.0
+     */
+    function quote($str)
+    {
+        return $this->quoteSmart($str);
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        if (function_exists('mysql_real_escape_string')) {
+            return @mysql_real_escape_string($str, $this->connection);
+        } else {
+            return @mysql_escape_string($str);
+        }
+    }
+
+    // }}}
+    // {{{ modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * This little hack lets you know how many rows were deleted
+     * when running a "DELETE FROM table" query.  Only implemented
+     * if the DB_PORTABILITY_DELETE_COUNT portability option is on.
+     *
+     * @param string $query  the query string to modify
+     *
+     * @return string  the modified query string
+     *
+     * @access protected
+     * @see DB_common::setOption()
+     */
+    function modifyQuery($query)
+    {
+        if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
+            // "DELETE FROM table" gives 0 affected rows in MySQL.
+            // This little hack lets you know how many rows were deleted.
+            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+                                      'DELETE FROM \1 WHERE 1=1', $query);
+            }
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        if (DB::isManip($query)) {
+            return $query . " LIMIT $count";
+        } else {
+            return $query . " LIMIT $from, $count";
+        }
+    }
+
+    // }}}
+    // {{{ mysqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_mysql::errorNative(), DB_common::errorCode()
+     */
+    function mysqlRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+                $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
+                $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
+                $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
+            } else {
+                // Doing this in case mode changes during runtime.
+                $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
+                $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
+                $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
+            }
+            $errno = $this->errorCode(mysql_errno($this->connection));
+        }
+        return $this->raiseError($errno, null, null, null,
+                                 @mysql_errno($this->connection) . ' ** ' .
+                                 @mysql_error($this->connection));
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code
+     */
+    function errorNative()
+    {
+        return @mysql_errno($this->connection);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @mysql_list_fields($this->dsn['database'],
+                                     $result, $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @mysql_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $case_func(@mysql_field_table($id, $i)),
+                'name'  => $case_func(@mysql_field_name($id, $i)),
+                'type'  => @mysql_field_type($id, $i),
+                'len'   => @mysql_field_len($id, $i),
+                'flags' => @mysql_field_flags($id, $i),
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @mysql_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SHOW TABLES';
+            case 'users':
+                return 'SELECT DISTINCT User FROM mysql.user';
+            case 'databases':
+                return 'SHOW DATABASES';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/repository/flickr/PEAR/DB/pgsql.php b/repository/flickr/PEAR/DB/pgsql.php
new file mode 100755 (executable)
index 0000000..ae229af
--- /dev/null
@@ -0,0 +1,1097 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's pgsql extension
+ * for interacting with PostgreSQL databases
+ *
+ * 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   Database
+ * @package    DB
+ * @author     Rui Hirokawa <hirokawa@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id$
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's pgsql extension
+ * for interacting with PostgreSQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Rui Hirokawa <hirokawa@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_pgsql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'pgsql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'pgsql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => '4.3.0',
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => true,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The number of rows affected by a data manipulation query
+     * @var integer
+     */
+    var $affected = 0;
+
+    /**
+     * The current row being looked at in fetchInto()
+     * @var array
+     * @access private
+     */
+    var $row = array();
+
+    /**
+     * The number of rows in a given result set
+     * @var array
+     * @access private
+     */
+    var $_num_rows = array();
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_pgsql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's pgsql driver supports the following extra DSN options:
+     *   + connect_timeout  How many seconds to wait for a connection to
+     *                       be established.  Available since PEAR DB 1.7.0.
+     *   + new_link         If set to true, causes subsequent calls to
+     *                       connect() to return a new connection link
+     *                       instead of the existing one.  WARNING: this is
+     *                       not portable to other DBMS's.  Available only
+     *                       if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0.
+     *   + options          Command line options to be sent to the server.
+     *                       Available since PEAR DB 1.6.4.
+     *   + service          Specifies a service name in pg_service.conf that
+     *                       holds additional connection parameters.
+     *                       Available since PEAR DB 1.7.0.
+     *   + sslmode          How should SSL be used when connecting?  Values:
+     *                       disable, allow, prefer or require.
+     *                       Available since PEAR DB 1.7.0.
+     *   + tty              This was used to specify where to send server
+     *                       debug output.  Available since PEAR DB 1.6.4.
+     *
+     * Example of connecting to a new link via a socket:
+     * <code>
+     * require_once 'DB.php';
+     * 
+     * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true';
+     * $options = array(
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     * 
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     *
+     * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('pgsql')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp';
+
+        $params = array('');
+        if ($protocol == 'tcp') {
+            if ($dsn['hostspec']) {
+                $params[0] .= 'host=' . $dsn['hostspec'];
+            }
+            if ($dsn['port']) {
+                $params[0] .= ' port=' . $dsn['port'];
+            }
+        } elseif ($protocol == 'unix') {
+            // Allow for pg socket in non-standard locations.
+            if ($dsn['socket']) {
+                $params[0] .= 'host=' . $dsn['socket'];
+            }
+            if ($dsn['port']) {
+                $params[0] .= ' port=' . $dsn['port'];
+            }
+        }
+        if ($dsn['database']) {
+            $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\'';
+        }
+        if ($dsn['username']) {
+            $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\'';
+        }
+        if ($dsn['password']) {
+            $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\'';
+        }
+        if (!empty($dsn['options'])) {
+            $params[0] .= ' options=' . $dsn['options'];
+        }
+        if (!empty($dsn['tty'])) {
+            $params[0] .= ' tty=' . $dsn['tty'];
+        }
+        if (!empty($dsn['connect_timeout'])) {
+            $params[0] .= ' connect_timeout=' . $dsn['connect_timeout'];
+        }
+        if (!empty($dsn['sslmode'])) {
+            $params[0] .= ' sslmode=' . $dsn['sslmode'];
+        }
+        if (!empty($dsn['service'])) {
+            $params[0] .= ' service=' . $dsn['service'];
+        }
+
+        if (isset($dsn['new_link'])
+            && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+        {
+            if (version_compare(phpversion(), '4.3.0', '>=')) {
+                $params[] = PGSQL_CONNECT_FORCE_NEW;
+            }
+        }
+
+        $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect';
+
+        $ini = ini_get('track_errors');
+        $php_errormsg = '';
+        if ($ini) {
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+        } else {
+            ini_set('track_errors', 1);
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+            ini_set('track_errors', $ini);
+        }
+
+        if (!$this->connection) {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     $php_errormsg);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @pg_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @pg_exec($this->connection, 'begin;');
+                if (!$result) {
+                    return $this->pgsqlRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        $result = @pg_exec($this->connection, $query);
+        if (!$result) {
+            return $this->pgsqlRaiseError();
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        if ($ismanip) {
+            $this->affected = @pg_affected_rows($result);
+            return DB_OK;
+        } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) {
+            /* PostgreSQL commands:
+               ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
+               CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
+               GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
+               REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
+               UNLISTEN, UPDATE, VACUUM
+            */
+            $this->row[(int)$result] = 0; // reset the row counter.
+            $numrows = $this->numRows($result);
+            if (is_object($numrows)) {
+                return $numrows;
+            }
+            $this->_num_rows[(int)$result] = $numrows;
+            $this->affected = 0;
+            return $result;
+        } else {
+            $this->affected = 0;
+            return DB_OK;
+        }
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal pgsql result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        $result_int = (int)$result;
+        $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int];
+        if ($rownum >= $this->_num_rows[$result_int]) {
+            return null;
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @pg_fetch_row($result, $rownum);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        $this->row[$result_int] = ++$rownum;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        if (is_resource($result)) {
+            unset($this->row[(int)$result]);
+            unset($this->_num_rows[(int)$result]);
+            $this->affected = 0;
+            return @pg_freeresult($result);
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * @deprecated  Deprecated in release 1.6.0
+     * @internal
+     */
+    function quote($str)
+    {
+        return $this->quoteSmart($str);
+    }
+
+    // }}}
+    // {{{ quoteSmart()
+
+    /**
+     * Formats input so it can be safely used in a query
+     *
+     * @param mixed $in  the data to be formatted
+     *
+     * @return mixed  the formatted data.  The format depends on the input's
+     *                 PHP type:
+     *                 + null = the string <samp>NULL</samp>
+     *                 + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
+     *                 + integer or double = the unquoted number
+     *                 + other (including strings and numeric strings) =
+     *                   the data escaped according to MySQL's settings
+     *                   then encapsulated between single quotes
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteSmart($in)
+    {
+        if (is_int($in) || is_double($in)) {
+            return $in;
+        } elseif (is_bool($in)) {
+            return $in ? 'TRUE' : 'FALSE';
+        } elseif (is_null($in)) {
+            return 'NULL';
+        } else {
+            return "'" . $this->escapeSimple($in) . "'";
+        }
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * {@internal PostgreSQL treats a backslash as an escape character,
+     * so they are escaped as well.
+     *
+     * Not using pg_escape_string() yet because it requires PostgreSQL
+     * to be at version 7.2 or greater.}}
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        return str_replace("'", "''", str_replace('\\', '\\\\', $str));
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @pg_numfields($result);
+        if (!$cols) {
+            return $this->pgsqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @pg_numrows($result);
+        if ($rows === null) {
+            return $this->pgsqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            // (disabled) hack to shut up error messages from libpq.a
+            //@fclose(@fopen("php://stderr", "w"));
+            $result = @pg_exec($this->connection, 'end;');
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->pgsqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            $result = @pg_exec($this->connection, 'abort;');
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->pgsqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        return $this->affected;
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_pgsql::createSequence(), DB_pgsql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $repeat = false;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result =& $this->query("SELECT NEXTVAL('${seqname}')");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+                $repeat = true;
+                $this->pushErrorHandling(PEAR_ERROR_RETURN);
+                $result = $this->createSequence($seq_name);
+                $this->popErrorHandling();
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+            } else {
+                $repeat = false;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        $result->free();
+        return $arr[0];
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_pgsql::nextID(), DB_pgsql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $result = $this->query("CREATE SEQUENCE ${seqname}");
+        return $result;
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_pgsql::nextID(), DB_pgsql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP SEQUENCE '
+                            . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        return "$query LIMIT $count OFFSET $from";
+    }
+
+    // }}}
+    // {{{ pgsqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_pgsql::errorNative(), DB_pgsql::errorCode()
+     */
+    function pgsqlRaiseError($errno = null)
+    {
+        $native = $this->errorNative();
+        if ($errno === null) {
+            $errno = $this->errorCode($native);
+        }
+        return $this->raiseError($errno, null, null, null, $native);
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error message produced by the last query
+     *
+     * {@internal Error messages are used instead of error codes 
+     * in order to support older versions of PostgreSQL.}}
+     *
+     * @return string  the DBMS' error message
+     */
+    function errorNative()
+    {
+        return @pg_errormessage($this->connection);
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Determines PEAR::DB error code from the database's text error message.
+     *
+     * @param  string  $errormsg  error message returned from the database
+     * @return integer  an error number from a DB error constant
+     */
+    function errorCode($errormsg)
+    {
+        static $error_regexps;
+        if (!isset($error_regexps)) {
+            $error_regexps = array(
+                '/(relation|sequence|table).*does not exist|class .* not found/i'
+                    => DB_ERROR_NOSUCHTABLE,
+                '/index .* does not exist/'
+                    => DB_ERROR_NOT_FOUND,
+                '/column .* does not exist/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/relation .* already exists/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/(divide|division) by zero$/i'
+                    => DB_ERROR_DIVZERO,
+                '/pg_atoi: error in .*: can\'t parse /i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/invalid input syntax for( type)? (integer|numeric)/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/value .* is out of range for type \w*int/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/integer out of range/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/value too long for type character/i'
+                    => DB_ERROR_INVALID,
+                '/attribute .* not found|relation .* does not have attribute/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/column .* specified in USING clause does not exist in (left|right) table/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/parser: parse error at or near/i'
+                    => DB_ERROR_SYNTAX,
+                '/syntax error at/'
+                    => DB_ERROR_SYNTAX,
+                '/column reference .* is ambiguous/i'
+                    => DB_ERROR_SYNTAX,
+                '/permission denied/'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/violates not-null constraint/'
+                    => DB_ERROR_CONSTRAINT_NOT_NULL,
+                '/violates [\w ]+ constraint/'
+                    => DB_ERROR_CONSTRAINT,
+                '/referential integrity violation/'
+                    => DB_ERROR_CONSTRAINT,
+                '/more expressions than target columns/i'
+                    => DB_ERROR_VALUE_COUNT_ON_ROW,
+            );
+        }
+        foreach ($error_regexps as $regexp => $code) {
+            if (preg_match($regexp, $errormsg)) {
+                return $code;
+            }
+        }
+        // Fall back to DB_ERROR if there was no mapping.
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+     * is a table name.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0");
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @pg_numfields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $got_string ? $case_func($result) : '',
+                'name'  => $case_func(@pg_fieldname($id, $i)),
+                'type'  => @pg_fieldtype($id, $i),
+                'len'   => @pg_fieldsize($id, $i),
+                'flags' => $got_string
+                           ? $this->_pgFieldFlags($id, $i, $result)
+                           : '',
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @pg_freeresult($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ _pgFieldFlags()
+
+    /**
+     * Get a column's flags
+     *
+     * Supports "not_null", "default_value", "primary_key", "unique_key"
+     * and "multiple_key".  The default value is passed through
+     * rawurlencode() in case there are spaces in it.
+     *
+     * @param int $resource   the PostgreSQL result identifier
+     * @param int $num_field  the field number
+     *
+     * @return string  the flags
+     *
+     * @access private
+     */
+    function _pgFieldFlags($resource, $num_field, $table_name)
+    {
+        $field_name = @pg_fieldname($resource, $num_field);
+
+        $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
+                                FROM pg_attribute f, pg_class tab, pg_type typ
+                                WHERE tab.relname = typ.typname
+                                AND typ.typrelid = f.attrelid
+                                AND f.attname = '$field_name'
+                                AND tab.relname = '$table_name'");
+        if (@pg_numrows($result) > 0) {
+            $row = @pg_fetch_row($result, 0);
+            $flags  = ($row[0] == 't') ? 'not_null ' : '';
+
+            if ($row[1] == 't') {
+                $result = @pg_exec($this->connection, "SELECT a.adsrc
+                                    FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a
+                                    WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
+                                    AND f.attrelid = a.adrelid AND f.attname = '$field_name'
+                                    AND tab.relname = '$table_name' AND f.attnum = a.adnum");
+                $row = @pg_fetch_row($result, 0);
+                $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]);
+                $flags .= 'default_' . rawurlencode($num) . ' ';
+            }
+        } else {
+            $flags = '';
+        }
+        $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey
+                                FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i
+                                WHERE tab.relname = typ.typname
+                                AND typ.typrelid = f.attrelid
+                                AND f.attrelid = i.indrelid
+                                AND f.attname = '$field_name'
+                                AND tab.relname = '$table_name'");
+        $count = @pg_numrows($result);
+
+        for ($i = 0; $i < $count ; $i++) {
+            $row = @pg_fetch_row($result, $i);
+            $keys = explode(' ', $row[2]);
+
+            if (in_array($num_field + 1, $keys)) {
+                $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : '';
+                $flags .= ($row[1] == 't') ? 'primary_key ' : '';
+                if (count($keys) > 1)
+                    $flags .= 'multiple_key ';
+            }
+        }
+
+        return trim($flags);
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SELECT c.relname AS "Name"'
+                        . ' FROM pg_class c, pg_user u'
+                        . ' WHERE c.relowner = u.usesysid'
+                        . " AND c.relkind = 'r'"
+                        . ' AND NOT EXISTS'
+                        . ' (SELECT 1 FROM pg_views'
+                        . '  WHERE viewname = c.relname)'
+                        . " AND c.relname !~ '^(pg_|sql_)'"
+                        . ' UNION'
+                        . ' SELECT c.relname AS "Name"'
+                        . ' FROM pg_class c'
+                        . " WHERE c.relkind = 'r'"
+                        . ' AND NOT EXISTS'
+                        . ' (SELECT 1 FROM pg_views'
+                        . '  WHERE viewname = c.relname)'
+                        . ' AND NOT EXISTS'
+                        . ' (SELECT 1 FROM pg_user'
+                        . '  WHERE usesysid = c.relowner)'
+                        . " AND c.relname !~ '^pg_'";
+            case 'schema.tables':
+                return "SELECT schemaname || '.' || tablename"
+                        . ' AS "Name"'
+                        . ' FROM pg_catalog.pg_tables'
+                        . ' WHERE schemaname NOT IN'
+                        . " ('pg_catalog', 'information_schema', 'pg_toast')";
+            case 'views':
+                // Table cols: viewname | viewowner | definition
+                return 'SELECT viewname from pg_views WHERE schemaname'
+                        . " NOT IN ('information_schema', 'pg_catalog')";
+            case 'users':
+                // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd  |valuntil
+                return 'SELECT usename FROM pg_user';
+            case 'databases':
+                return 'SELECT datname FROM pg_database';
+            case 'functions':
+            case 'procedures':
+                return 'SELECT proname FROM pg_proc WHERE proowner <> 1';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/repository/flickr/PEAR/DB/storage.php b/repository/flickr/PEAR/DB/storage.php
new file mode 100755 (executable)
index 0000000..17bffd1
--- /dev/null
@@ -0,0 +1,504 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Provides an object interface to a table row
+ *
+ * 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   Database
+ * @package    DB
+ * @author     Stig Bakken <stig@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id$
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB class so it can be extended from
+ */
+require_once 'DB.php';
+
+/**
+ * Provides an object interface to a table row
+ *
+ * It lets you add, delete and change rows using objects rather than SQL
+ * statements.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <stig@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_storage extends PEAR
+{
+    // {{{ properties
+
+    /** the name of the table (or view, if the backend database supports
+        updates in views) we hold data from */
+    var $_table = null;
+
+    /** which column(s) in the table contains primary keys, can be a
+        string for single-column primary keys, or an array of strings
+        for multiple-column primary keys */
+    var $_keycolumn = null;
+
+    /** DB connection handle used for all transactions */
+    var $_dbh = null;
+
+    /** an assoc with the names of database fields stored as properties
+        in this object */
+    var $_properties = array();
+
+    /** an assoc with the names of the properties in this object that
+        have been changed since they were fetched from the database */
+    var $_changes = array();
+
+    /** flag that decides if data in this object can be changed.
+        objects that don't have their table's key column in their
+        property lists will be flagged as read-only. */
+    var $_readonly = false;
+
+    /** function or method that implements a validator for fields that
+        are set, this validator function returns true if the field is
+        valid, false if not */
+    var $_validator = null;
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * Constructor
+     *
+     * @param $table string the name of the database table
+     *
+     * @param $keycolumn mixed string with name of key column, or array of
+     * strings if the table has a primary key of more than one column
+     *
+     * @param $dbh object database connection object
+     *
+     * @param $validator mixed function or method used to validate
+     * each new value, called with three parameters: the name of the
+     * field/column that is changing, a reference to the new value and
+     * a reference to this object
+     *
+     */
+    function DB_storage($table, $keycolumn, &$dbh, $validator = null)
+    {
+        $this->PEAR('DB_Error');
+        $this->_table = $table;
+        $this->_keycolumn = $keycolumn;
+        $this->_dbh = $dbh;
+        $this->_readonly = false;
+        $this->_validator = $validator;
+    }
+
+    // }}}
+    // {{{ _makeWhere()
+
+    /**
+     * Utility method to build a "WHERE" clause to locate ourselves in
+     * the table.
+     *
+     * XXX future improvement: use rowids?
+     *
+     * @access private
+     */
+    function _makeWhere($keyval = null)
+    {
+        if (is_array($this->_keycolumn)) {
+            if ($keyval === null) {
+                for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
+                    $keyval[] = $this->{$this->_keycolumn[$i]};
+                }
+            }
+            $whereclause = '';
+            for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
+                if ($i > 0) {
+                    $whereclause .= ' AND ';
+                }
+                $whereclause .= $this->_keycolumn[$i];
+                if (is_null($keyval[$i])) {
+                    // there's not much point in having a NULL key,
+                    // but we support it anyway
+                    $whereclause .= ' IS NULL';
+                } else {
+                    $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
+                }
+            }
+        } else {
+            if ($keyval === null) {
+                $keyval = @$this->{$this->_keycolumn};
+            }
+            $whereclause = $this->_keycolumn;
+            if (is_null($keyval)) {
+                // there's not much point in having a NULL key,
+                // but we support it anyway
+                $whereclause .= ' IS NULL';
+            } else {
+                $whereclause .= ' = ' . $this->_dbh->quote($keyval);
+            }
+        }
+        return $whereclause;
+    }
+
+    // }}}
+    // {{{ setup()
+
+    /**
+     * Method used to initialize a DB_storage object from the
+     * configured table.
+     *
+     * @param $keyval mixed the key[s] of the row to fetch (string or array)
+     *
+     * @return int DB_OK on success, a DB error if not
+     */
+    function setup($keyval)
+    {
+        $whereclause = $this->_makeWhere($keyval);
+        $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
+        $sth = $this->_dbh->query($query);
+        if (DB::isError($sth)) {
+            return $sth;
+        }
+        $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
+        if (DB::isError($row)) {
+            return $row;
+        }
+        if (!$row) {
+            return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+                                     $query, null, true);
+        }
+        foreach ($row as $key => $value) {
+            $this->_properties[$key] = true;
+            $this->$key = $value;
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ insert()
+
+    /**
+     * Create a new (empty) row in the configured table for this
+     * object.
+     */
+    function insert($newpk)
+    {
+        if (is_array($this->_keycolumn)) {
+            $primarykey = $this->_keycolumn;
+        } else {
+            $primarykey = array($this->_keycolumn);
+        }
+        settype($newpk, "array");
+        for ($i = 0; $i < sizeof($primarykey); $i++) {
+            $pkvals[] = $this->_dbh->quote($newpk[$i]);
+        }
+
+        $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
+                                  implode(",", $primarykey) . ") VALUES(" .
+                                  implode(",", $pkvals) . ")");
+        if (DB::isError($sth)) {
+            return $sth;
+        }
+        if (sizeof($newpk) == 1) {
+            $newpk = $newpk[0];
+        }
+        $this->setup($newpk);
+    }
+
+    // }}}
+    // {{{ toString()
+
+    /**
+     * Output a simple description of this DB_storage object.
+     * @return string object description
+     */
+    function toString()
+    {
+        $info = strtolower(get_class($this));
+        $info .= " (table=";
+        $info .= $this->_table;
+        $info .= ", keycolumn=";
+        if (is_array($this->_keycolumn)) {
+            $info .= "(" . implode(",", $this->_keycolumn) . ")";
+        } else {
+            $info .= $this->_keycolumn;
+        }
+        $info .= ", dbh=";
+        if (is_object($this->_dbh)) {
+            $info .= $this->_dbh->toString();
+        } else {
+            $info .= "null";
+        }
+        $info .= ")";
+        if (sizeof($this->_properties)) {
+            $info .= " [loaded, key=";
+            $keyname = $this->_keycolumn;
+            if (is_array($keyname)) {
+                $info .= "(";
+                for ($i = 0; $i < sizeof($keyname); $i++) {
+                    if ($i > 0) {
+                        $info .= ",";
+                    }
+                    $info .= $this->$keyname[$i];
+                }
+                $info .= ")";
+            } else {
+                $info .= $this->$keyname;
+            }
+            $info .= "]";
+        }
+        if (sizeof($this->_changes)) {
+            $info .= " [modified]";
+        }
+        return $info;
+    }
+
+    // }}}
+    // {{{ dump()
+
+    /**
+     * Dump the contents of this object to "standard output".
+     */
+    function dump()
+    {
+        foreach ($this->_properties as $prop => $foo) {
+            print "$prop = ";
+            print htmlentities($this->$prop);
+            print "<br />\n";
+        }
+    }
+
+    // }}}
+    // {{{ &create()
+
+    /**
+     * Static method used to create new DB storage objects.
+     * @param $data assoc. array where the keys are the names
+     *              of properties/columns
+     * @return object a new instance of DB_storage or a subclass of it
+     */
+    function &create($table, &$data)
+    {
+        $classname = strtolower(get_class($this));
+        $obj =& new $classname($table);
+        foreach ($data as $name => $value) {
+            $obj->_properties[$name] = true;
+            $obj->$name = &$value;
+        }
+        return $obj;
+    }
+
+    // }}}
+    // {{{ loadFromQuery()
+
+    /**
+     * Loads data into this object from the given query.  If this
+     * object already contains table data, changes will be saved and
+     * the object re-initialized first.
+     *
+     * @param $query SQL query
+     *
+     * @param $params parameter list in case you want to use
+     * prepare/execute mode
+     *
+     * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
+     * returned object is read-only (because the object's specified
+     * key column was not found among the columns returned by $query),
+     * or another DB error code in case of errors.
+     */
+// XXX commented out for now
+/*
+    function loadFromQuery($query, $params = null)
+    {
+        if (sizeof($this->_properties)) {
+            if (sizeof($this->_changes)) {
+                $this->store();
+                $this->_changes = array();
+            }
+            $this->_properties = array();
+        }
+        $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
+        if (DB::isError($rowdata)) {
+            return $rowdata;
+        }
+        reset($rowdata);
+        $found_keycolumn = false;
+        while (list($key, $value) = each($rowdata)) {
+            if ($key == $this->_keycolumn) {
+                $found_keycolumn = true;
+            }
+            $this->_properties[$key] = true;
+            $this->$key = &$value;
+            unset($value); // have to unset, or all properties will
+                           // refer to the same value
+        }
+        if (!$found_keycolumn) {
+            $this->_readonly = true;
+            return DB_WARNING_READ_ONLY;
+        }
+        return DB_OK;
+    }
+ */
+
+    // }}}
+    // {{{ set()
+
+    /**
+     * Modify an attriute value.
+     */
+    function set($property, $newvalue)
+    {
+        // only change if $property is known and object is not
+        // read-only
+        if ($this->_readonly) {
+            return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
+                                     null, null, null, true);
+        }
+        if (@isset($this->_properties[$property])) {
+            if (empty($this->_validator)) {
+                $valid = true;
+            } else {
+                $valid = @call_user_func($this->_validator,
+                                         $this->_table,
+                                         $property,
+                                         $newvalue,
+                                         $this->$property,
+                                         $this);
+            }
+            if ($valid) {
+                $this->$property = $newvalue;
+                if (empty($this->_changes[$property])) {
+                    $this->_changes[$property] = 0;
+                } else {
+                    $this->_changes[$property]++;
+                }
+            } else {
+                return $this->raiseError(null, DB_ERROR_INVALID, null,
+                                         null, "invalid field: $property",
+                                         null, true);
+            }
+            return true;
+        }
+        return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
+                                 null, "unknown field: $property",
+                                 null, true);
+    }
+
+    // }}}
+    // {{{ &get()
+
+    /**
+     * Fetch an attribute value.
+     *
+     * @param string attribute name
+     *
+     * @return attribute contents, or null if the attribute name is
+     * unknown
+     */
+    function &get($property)
+    {
+        // only return if $property is known
+        if (isset($this->_properties[$property])) {
+            return $this->$property;
+        }
+        $tmp = null;
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ _DB_storage()
+
+    /**
+     * Destructor, calls DB_storage::store() if there are changes
+     * that are to be kept.
+     */
+    function _DB_storage()
+    {
+        if (sizeof($this->_changes)) {
+            $this->store();
+        }
+        $this->_properties = array();
+        $this->_changes = array();
+        $this->_table = null;
+    }
+
+    // }}}
+    // {{{ store()
+
+    /**
+     * Stores changes to this object in the database.
+     *
+     * @return DB_OK or a DB error
+     */
+    function store()
+    {
+        foreach ($this->_changes as $name => $foo) {
+            $params[] = &$this->$name;
+            $vars[] = $name . ' = ?';
+        }
+        if ($vars) {
+            $query = 'UPDATE ' . $this->_table . ' SET ' .
+                implode(', ', $vars) . ' WHERE ' .
+                $this->_makeWhere();
+            $stmt = $this->_dbh->prepare($query);
+            $res = $this->_dbh->execute($stmt, $params);
+            if (DB::isError($res)) {
+                return $res;
+            }
+            $this->_changes = array();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ remove()
+
+    /**
+     * Remove the row represented by this object from the database.
+     *
+     * @return mixed DB_OK or a DB error
+     */
+    function remove()
+    {
+        if ($this->_readonly) {
+            return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
+                                     null, null, null, true);
+        }
+        $query = 'DELETE FROM ' . $this->_table .' WHERE '.
+            $this->_makeWhere();
+        $res = $this->_dbh->query($query);
+        if (DB::isError($res)) {
+            return $res;
+        }
+        foreach ($this->_properties as $prop => $foo) {
+            unset($this->$prop);
+        }
+        $this->_properties = array();
+        $this->_changes = array();
+        return DB_OK;
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/repository/flickr/PEAR/HTTP/Request.php b/repository/flickr/PEAR/HTTP/Request.php
new file mode 100755 (executable)
index 0000000..a178c9d
--- /dev/null
@@ -0,0 +1,1484 @@
+<?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$
+ * @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
+?>
diff --git a/repository/flickr/PEAR/HTTP/Request/Listener.php b/repository/flickr/PEAR/HTTP/Request/Listener.php
new file mode 100755 (executable)
index 0000000..b6c3fac
--- /dev/null
@@ -0,0 +1,106 @@
+<?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$
+ * @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);
+        }
+    }
+}
+?>
diff --git a/repository/flickr/PEAR/Net/Socket.php b/repository/flickr/PEAR/Net/Socket.php
new file mode 100755 (executable)
index 0000000..65f368d
--- /dev/null
@@ -0,0 +1,528 @@
+<?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$
+
+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;
+    }
+
+}
diff --git a/repository/flickr/PEAR/Net/URL.php b/repository/flickr/PEAR/Net/URL.php
new file mode 100755 (executable)
index 0000000..a39a7b3
--- /dev/null
@@ -0,0 +1,410 @@
+<?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$
+//
+// 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;
+    }
+
+}
+?>
diff --git a/repository/flickr/PEAR/PEAR.php b/repository/flickr/PEAR/PEAR.php
new file mode 100755 (executable)
index 0000000..a2f6510
--- /dev/null
@@ -0,0 +1,1108 @@
+<?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$
+ * @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:
+ */
+?>
diff --git a/repository/flickr/phpFlickr.php b/repository/flickr/phpFlickr.php
new file mode 100755 (executable)
index 0000000..4936078
--- /dev/null
@@ -0,0 +1,1417 @@
+<?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 __construct ($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);
+            echo $this->secret . "api_key" . $this->api_key . "perms" . $perms;
+                       $api_sig = md5($this->secret . "api_key" . $this->api_key . "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);
+                               echo '<a href="'."http://www.flickr.com/services/auth/?api_key=" . $this->api_key . "&extra=" . $redirect . "&perms=" .  $perms . "&api_sig=". $api_sig . '">Login</a>';
+                       }
+                       //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;
+       }
+}
+
+
+?>
diff --git a/repository/flickr/repository.class.php b/repository/flickr/repository.class.php
new file mode 100755 (executable)
index 0000000..4fe4e47
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/**
+ * repository_flickr class
+ * This is a subclass of repository class
+ *
+ * @author Dongsheng Cai
+ * @version 0.1 dev
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+require_once($CFG->dirroot.'/repository/lib.php');
+require_once($CFG->dirroot.'/repository/flickr/'.'phpFlickr.php');
+
+class repository_flickr extends repository{
+    var $flickr;
+    var $photos;
+    public function __construct($repositoryid, $context = SITEID, $options = array()){
+        global $SESSION, $action;
+        $options['page']    = optional_param('p', 1, PARAM_INT);
+        $options['api_key'] = 'bf85ae2b5b105a2c645f32a32cd6ad59';
+        $options['secret']  = '7cb2f9d7cf70aebe';
+        parent::__construct($repositoryid, $context, $options);
+        $this->flickr = new phpFlickr($this->options['api_key'], $this->options['secret']);
+
+        $reset = optional_param('reset', 0, PARAM_INT);
+        if(!empty($reset)) {
+            unset($SESSION->flickrmail);
+            set_user_preference('flickrmail', '');
+        }
+
+        if(!empty($SESSION->filckrmail)){
+            $action = 'list';
+        } else {
+            $options['flickrmail'] = optional_param('flickrmail', '', PARAM_RAW);
+            if(!empty($options['flickrmail'])) {
+                $people = $this->flickr->people_findByEmail($options['flickrmail']);
+                if(!empty($people)) {
+                    $remember = optional_param('remember', '', PARAM_RAW);
+                    if(!empty($remember)) {
+                        set_user_preference('flickrmail', $options['flickrmail']);
+                    }
+                    $SESSION->flickrmail = $options['flickrmail'];
+                    $action = 'list';
+                }
+            } else {
+                if($account = get_user_preferences('flickrmail', '')){
+                    $SESSION->flickrmail = $account;
+                    $action = 'list';
+                }
+            }
+        }
+    }
+    public function print_login(){
+        global $SESSION;
+        if(empty($SESSION->flickrmail)) {
+        echo <<<EOD
+            <form action="picker.php">
+            <label for="account">Flickr Account (Email)</lable>
+            <input type='text' name='flickrmail' id='account' />
+            <input type='hidden' name='id' value='$this->repositoryid' />
+            <input type='checkbox' name='remember' value='true' /> Remember <br/>
+            <input type='submit' value='Go' />
+            </form>
+EOD;
+        } else {
+            $this->print_listing();
+        }
+        //echo '<a href="?id='.$this->repositoryid.'&action=list">See flickr photos list</a>';
+        return true;
+    }
+    public function get_listing($path = '0', $search = ''){
+        global $SESSION;
+        $people = $this->flickr->people_findByEmail($SESSION->flickrmail);
+        $photos_url = $this->flickr->urls_getUserPhotos($people['nsid']);
+        $photos = $this->flickr->people_getPublicPhotos($people['nsid'], null, 36, $this->page);
+        $this->photos = array('a'=>$SESSION->flickrmail, 'u'=>$photos_url, 'p'=>$photos);
+        return $this->photos;
+    }
+    public function print_listing(){
+        if(empty($this->photos)){
+            $this->get_listing();
+        }
+        echo '<h2>Account: <span>'.$this->photos['a'].'</span></h2>';
+        echo '<a href="picker.php?id='.$this->repositoryid.'&reset=1">Change user</a>';
+        echo '<hr/>';
+        foreach ((array)$this->photos['p']['photo'] as $photo) {
+            echo "<a href='".$this->photos['u'].$photo[id]."'>";
+            echo "<img border='0' alt='$photo[title]' ".
+                "src=" . $this->flickr->buildPhotoURL($photo, "Square") . ">";
+            echo "</a>";
+            $i++;
+            // If it reaches the sixth photo, insert a line break
+            if ($i % 6 == 0) {
+                echo "<br/>";
+            }
+        }
+        echo <<<EOD
+<style type='text/css'>
+#paging{margin-top: 10px; clear:both}
+#paging a{padding: 4px; border: 1px solid gray}
+</style>
+EOD;
+        echo '<div id="paging">';
+        for($i=1; $i <= $this->photos['p']['pages']; $i++) {
+            echo '<a href="picker.php?id='.$this->repositoryid.'&action=list&p='.$i.'">';
+            echo $i;
+            echo '</a> ';
+        }
+        echo '</div>';
+    }
+    public function print_search(){
+        echo '<input type="text" name="Search" value="search terms..." size="40" class="right"/>';
+        return true;
+    }
+}
+?>
index 3b137fbe4ee43bc1b59c36944338ab0abc4ee01e..bb22923f168ab2dc3c9d0f7e720814b6f26e4fd0 100644 (file)
@@ -2,7 +2,7 @@
 require_once('../config.php');
 require_once('lib.php');
 $id        = required_param('id', PARAM_INT);
-$action    = optional_param('action', 'check', PARAM_RAW);
+$action    = optional_param('action', '', PARAM_RAW);
 if(!$repository = $DB->get_record('repository', array('id'=>$id))) {
     print_error('invalidrepostoryid');
 }
@@ -14,8 +14,6 @@ if(is_file($CFG->dirroot.'/repository/'.$repository->repositorytype.'/repository
 } else {
     print_error('invalidplugin', 'repository');
 }
-
-
 ?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html>
@@ -34,17 +32,17 @@ if(is_file($CFG->dirroot.'/repository/'.$repository->repositorytype.'/repository
                 <li><a href="###" class="link">Local Server</a></li>
                 <li><a href="###">Remote Moodle</a></li>
                 <li class='line'>------------------------------</li>
-                <li><a href="?repo=boxnet">Box.net</a></li>
-                <li><a href="###">Briefcase</a></li>
-                <li><a href="###">Door</a></li>
-                <li><a href="###">Flickr</a></li>
-                <li><a href="###">Google Docs</a></li>
-                <li><a href="###">Mahara</a></li>
-                <li><a href="###">Merlot</a></li>
-                <li><a href="###">Myspace</a></li>
-                <li><a href="###">Oki</a></li>
-                <li><a href="###">Skydrive</a></li>
-                <li><a href="###">Youtube</a></li>
+                <li><a href="?id=1">Box.net</a></li>
+                <li><a href="?id=2">Flickr</a></li>
+                <li><a href="###"><strike>Briefcase</strike></a></li>
+                <li><a href="###"><strike>Door</strike></a></li>
+                <li><a href="###"><strike>Google Docs</strike></a></li>
+                <li><a href="###"><strike>Mahara</strike></a></li>
+                <li><a href="###"><strike>Merlot</strike></a></li>
+                <li><a href="###"><strike>Myspace</strike></a></li>
+                <li><a href="###"><strike>Oki</strike></a></li>
+                <li><a href="###"><strike>Skydrive</strike></a></li>
+                <li><a href="###"><strike>Youtube</strike></a></li>
                 <li class='line'>------------------------------</li>
             </ul>
         </td>
@@ -55,13 +53,15 @@ if(is_file($CFG->dirroot.'/repository/'.$repository->repositorytype.'/repository
                 <img src="<?php echo $CFG->pixpath.'/moodlelogo.gif';?>" alt="Manage Google Docs" />
             </td>
             <td class="header">
-                <input type="text" name="Search" value="search terms..." size="40" class="right"/>
+                <?php
+                $repo->print_search();
+                ?>
             </td>
         </tr>
         </table>
         <div>
         <?php
-            if(!empty($action)) {
+            if($action == 'list') {
                 $repo->print_listing();
             } else {
                 $repo->print_login();
@@ -69,13 +69,13 @@ if(is_file($CFG->dirroot.'/repository/'.$repository->repositorytype.'/repository
         ?>
         </div>
         <!--
-            <iframe src="ibrowse.php" width="100%" height='250px' class="frame"></iframe>
-        -->
+        <iframe src="ibrowse.php" width="100%" height='250px' class="frame"></iframe>
         <div class="right">
         <input type="submit" value="Select" name="select"  />
         &nbsp;&nbsp;
         <input type="submit" value="Cancel" name="cancel"/>
         </div>
+        -->
         </td>
         </tr>
     </tbody>
index 01583944e54d648dbb8cf7938234d716aa6dd97a..5f0ef4cd13ab2032bc3061b1d60d4a87c96d8e15 100644 (file)
@@ -1,5 +1,5 @@
 body {
-       font: normal 12px auto "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
+       font: normal 16px auto "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
        color: #4f6b72;
        background: #E6EAE9;
 }
@@ -86,7 +86,7 @@ td.second {
     line-height: 1.5em;
 }
 #repolist li{
-    background: url(images/arrow.png) no-repeat 0 0;
+    background: url(images/arrow.png) no-repeat 0 6px;
     padding:0 0 0 16px;
 }
 #repolist .line{