code:
## Authentication

### HTTP Basic authentication

The API uses [HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
Note that this means that users with only an OpenID login cannot use the API; they have to add a
password to their account using the control panel on the site.

### OAuth authentication

OAuth 1.0a authentication for API resources is also supported. Generally, StatusNet's
UI and API are similar to Twitter's for OAuth applications (if you're new to OAuth
check out [Beginner’s Guide to OAuth](http://hueniverse.com/oauth/)).

To use OAuth, you'll need to register your client application via the web interface
and obtain a consumer key and secret. You can find the interface for application
registration at [%%action.oauthappssettings%%](%%action.oauthappssettings%%).

## JSONP callbacks

For API methods that return [JSON](https://en.wikipedia.org/wiki/JSON), an optional
JSONP-style callback parameter is supported. If supplied, the response will be in
JSONP format with a callback of the given name. To make it easier for clients to
handle error conditions, HTTP error codes are suppressed, and the errors will be
returned in the response body when using JSONP.

## Rate limiting

There is currently no rate-limiting.

## Gotchas

Some things to remember:

* %%site.name%% supports the
  [OStatus federation protocol](https://en.wikipedia.org/wiki/OStatus) (as well as
  [OpenMicroBlogging](https://en.wikipedia.org/wiki/OpenMicroBlogging) for backwards
  compatibility), so many notices and friends' profiles may come from other servers.
* User nicknames are unique, but they are not globally unique. Use the ID number
  instead.
* Private streams are not implemented yet.
* GNU social sites can be configured as private. In that case, all API methods
  require authentication, including the public timeline (see the 'config' method
  below).
* If "Fancy URLs" are not enabled, urls from above need to include "index.php" at
  the root. ( e.g. http://example.org/statusnet/api becomes http://www.example.org/statusnet/index.php/api )
* The `since_id` parameter does not work as documented by Twitter. Twitter says of
  `since_id`: "There are limits to the number of Tweets which can be accessed
  through the API. If the limit of Tweets has occured since the `since_id`, the
  `since_id` will be forced to the oldest ID available." However, GNU social will
  return the newest notices (or the newest back from max_id, if present)! Also, a
  `since_id` <= 0 will be ignored.

## Timeline resources

### statuses/public_timeline

Returns the 20 most recent notices, including repeats if they exist, from
non-protected users.

### statuses/home_timeline

Returns the 20 most recent notices, including repeats if they exist, posted by the
authenticating user and the users they follow. This is the same timeline seen by a
user when they login to their instance. This method is identical to
statuses/friends_timeline, except that this method always includes repeats.

### statuses/friends_timeline

Alias of statuses/home_timeline

### statuses/friends_timeline/:username

Alias of statuses/home_timeline for the specified username

### statuses/mentions

Returns the 20 most recent mentions (notices containing @username) for the
authenticating user.

This method will not include repeats in the XML and JSON responses unless the
include_rts parameter is set. The RSS and Atom responses will always include repeats
as notices prefixed with RT.

### statuses/replies

Alias of statuses/mentions

### statuses/replies/:username

Alias of statuses/mentions for the specified username

### statuses/user_timeline

Returns the 20 most recent notices posted by the authenticating user. It is also
possible to request another user's timeline by using the screen\_name or user_id
parameter. The other users timeline will only be visible if they are not protected,
or if the authenticating user's follow request was accepted by the protected user.

This method will not include repeats in the XML and JSON responses unless the
include_rts parameter is set. The RSS and Atom responses will always include
repeats as notices prefixed with RT, regardless of provided parameters.

### statuses/retweeted\_to_me

Not implemented.

### statuses/retweeted\_by_me

Not implemented.

### statuses/retweets\_of_me

Not implemented.

## Status resources

### statuses/show/:id

Returns a single notice, specified by the id parameter. The notice's author will be
returned inline.

### statuses/update

Post a new notice as the authenticating user.

Additional 'media' parameter allows binary multimedia uploads (images, etc.). Format
post data as multipart/form-data when using the 'media' parameter.

### statuses/destroy/:id

Destroys the notice specified by the required ID parameter. The authenticating user
must be the author of the specified notice. Returns the destroyed notice if successful.

### statuses/retweet/:id

Repeats a notice. Returns the original notice with repeat details embedded.

## User resources

### statuses/friends

Returns the user's subscriptions (friends) as an array of profiles.

### statuses/followers

Returns the user's subscribers (followers) as an array of profiles.

### users/show

Returns extended information of a given user, specified by ID or screen name as per
the required id parameter.

## Direct message resources

### direct_messages

Returns the 20 most recent direct messages sent to the authenticating user. The XML
and JSON versions include detailed information about the sender and recipient user.

### direct_messages/sent

Returns the 20 most recent direct messages sent by the authenticating user. The XML
and JSON versions include detailed information about the sender and recipient user.

### direct_messages/new

Sends a new direct message to the specified user from the authenticating user.
Requires both the user and text parameters and must be a POST. Returns the sent
message in the requested format if successful.

### direct_messages/destroy

Not implemented.

## Friendships resources

### friendships/create

Allows the authenticating users to follow the user specified in the ID parameter.
Returns the befriended user in the requested format when successful. Returns a
string describing the failure condition when unsuccessful.

If you are already friends with the user a HTTP 403 may be returned, though for
performance reasons you may get a 200 OK message even if the friendship already
exists.

Note that users cannot subscribe to remote profiles using this API.

### friendships/destroy

Allows the authenticating users to unfollow the user specified in the ID parameter.
Returns the unfollowed user in the requested format when successful. Returns a
string describing the failure condition when unsuccessful.

Users can unsubscribe to a remote profile using this API, but it's preferred to use
numeric IDs to nicknames.

### friendships/exists

Test for the existence of friendship between two users. Will return true if user\_a
follows user_b, otherwise will return false. Authentication is required if either
user A or user B are protected. Additionally the authenticating user must be a
follower of the protected user.

### friendships/show

Returns detailed information about the relationship between two users.

## Friends and subscribers resources

### friends/ids

Returns an array of numeric IDs for every user the specified user is subscribed to.
This method is powerful when used in conjunction with users/lookup.

### followers/ids

Returns an array of numeric IDs for every user subscsribed to the specified user.
This method is powerful when used in conjunction with users/lookup.

## Account resources

### account/verify_credentials

Returns an HTTP 200 OK response code and a representation of the requesting user if
authentication was successful; returns a 401 status code and an error message if
not. Use this method to test if supplied user credentials are valid.

### account/end_session

Not implemented.

### account/update\_delivery_device

Not implemented.

### account/rate\_limit_status

Returns the remaining number of API requests available to the requesting user before
the API limit is reached.

We have no rate limit, so this always returns 150 hits left.

### account/update\_profile\_background_image

Updates the authenticating user's profile background image. This method can also be
used to enable or disable the profile background image.

### account/update\_profile_image

Updates the authenticating user's profile image. Note that this method expects raw
multipart data, not a URL to an image.

## Favorite resources

### favorites

Returns the 20 most recent favorite statuses for the authenticating or specified
user in the requested format.

### favorites/create/:id

Favorites the status specified in the ID parameter as the authenticating user.
Returns the favorite status when successful.

### favorites/destroy/:id

Un-favorites the status specified in the ID parameter as the authenticating user.
Returns the un-favorited status in the requested format when successful.

## Notification resources

### notifications/follow

Not implemented.

### notifications/leave

Not implemented.

## Block resources

### blocks/create

Blocks the specified user from following the authenticating user. In addition the
blocked user will not show in the authenticating users mentions or timeline (unless
retweeted by another user). If a follow or friend relationship exists it is
destroyed.

### blocks/destroy

Un-blocks the user specified in the ID parameter for the authenticating user.
Returns the un-blocked user in the requested format when successful. If
relationships existed before the block was instated, they will not be restored.

### blocks/exists

Not implemented.

### blocks/blocking

Not implemented.

## Help resources

### help/test

Returns the string "ok" in the requested format with a 200 OK HTTP status code. This
method is great for sending a HEAD request to determine our servers current time.

## OAuth resources

It is strongly recommended you use HTTPS for all OAuth authorization steps.

### oauth/request_token

Allows a Consumer application to obtain an OAuth Request Token to request user
authorization. This method fulfills Section 6.1 of the OAuth 1.0 authentication
flow. It is strongly recommended you use HTTPS for all OAuth authorization steps.

### oauth/authorize

Allows a Consumer application to use an OAuth Request Token to request user
authorization. This method fulfills Section 6.2 of the OAuth 1.0 authentication
flow. Desktop applications must use this method (and cannot use GET oauth/authenticate).

### oauth/access_token

Allows a Consumer application to exchange the OAuth Request Token for an OAuth
Access Token. This method fulfills Section 6.3 of the OAuth 1.0 authentication flow.
The OAuth access token may also be used for xAuth operations.

## Search

The search method supports the following optional URL parameters:

* **callback**: if supplied when using the JSON format, the response will use the
  JSONP format with a callback of the given name.
* **rpp**: the number of notices to return per page, up to a max of 100.
* **page**: the page number (starting at 1) to return.
* **since_id:**: returns notices with ids greater than the given id.

Note:

* The search does not support operators, such as "from:", "to:" and booleans.
* Notice content is HTML-encoded.

### search

Returns relevant notices that match a specified query.

### Atom

To request search results in Atom, append your URL-encoded query as a parameter to
the search method and specify the Atom format:

`%%site.server%%/%%site.path%%api/search.atom?q=<query>`

### JSON

To request search results in JSON, append your URL-encoded query as a parameter to
the search method and specify the JSON format:

`%%site.server%%/%%site.path%%api/search.json?q=<query>`

## Additional resources

These are extensions to the Twitter API that expose additional functionality.

### Group resources

#### statusnet/groups/timeline

Shows a group's timeline. Similar to other timeline resources.

#### statusnet/groups/show

Show a groups profile.

#### statusnet/groups/create

Create a new group.

#### statusnet/groups/join

Join a group.

#### statusnet/groups/leave

Leave a group.

#### statusnet/groups/list

Show the groups a given user is a member of.

#### statusnet/groups/list_all

List all local groups.

#### statusnet/groups/membership

List the members of a given group.

#### statusnet/groups/is_member

Determine whether a given user is a member of a given group.

### Tag resources

#### statusnet/tags/timeline

Shows a tag's timeline. Similar to other timeline resources.

### Media resources

#### statusnet/media/upload

Endpoint for uploading an image. Returns a URL that can be used in a status update.
Format post data as multipart/form-data.

### Configuration

#### statusnet/config

Show an instance's configuration information.

Of special note is the `<private>` element (config/site/private), which indicates
whether a site is private. When a site is configured as private every other API
method requires authentication, including the public timeline (`/api/statuses/public_timeline.format`).

path: doc-src/twitterapi


code:
<?php
/**
 * Note : Code is released under the GNU LGPL
 *
 * Please do not change the header of this file
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU Lesser General Public License for more details.
 */

/**
 * The AcceptHeader page will parse and sort the different
 * allowed types for the content negociations
 *
 * @author Pierrick Charron <pierrick@webstart.fr>
 */
class AcceptHeader extends \ArrayObject
{
    /**
     * Constructor
     *
     * @param string $header Value of the Accept header
     * @return void
     */
    public function __construct($header)
    {
        $acceptedTypes = $this->_parse($header);
        usort($acceptedTypes, [$this, '_compare']);
        parent::__construct($acceptedTypes);
    }

    /**
     * Parse the accept header and return an array containing
     * all the informations about the Accepted types
     *
     * @param string $data Value of the Accept header
     * @return array
     */
    private function _parse($data)
    {
        $array = [];
        $items = explode(',', $data);
        foreach ($items as $item) {
            $elems = explode(';', $item);

            $mime  = current($elems);
            $types = explode('/', $mime);

            if (!isset($types[1])) {
                continue;
            }

            $acceptElement = [
                'raw'     => $mime,
                'type'    => trim($types[0]),
                'subtype' => trim($types[1]),
            ];

            $acceptElement['params'] = [];
            while (next($elems)) {
                list($name, $value) = explode('=', current($elems));
                $acceptElement['params'][trim($name)] = trim($value);
            }

            $array[] = $acceptElement;
        }
        return $array;
    }

    /**
     * Compare two Accepted types with their parameters to know
     * if one media type should be used instead of an other
     *
     * @param array $a The first media type and its parameters
     * @param array $b The second media type and its parameters
     * @return int
     */
    private function _compare($a, $b)
    {
        $a_q = isset($a['params']['q']) ? floatval($a['params']['q']) : 1.0;
        $b_q = isset($b['params']['q']) ? floatval($b['params']['q']) : 1.0;
        if ($a_q === $b_q) {
            $a_count = count($a['params']);
            $b_count = count($b['params']);
            if ($a_count === $b_count) {
                if ($r = $this->_compareSubType($a['subtype'], $b['subtype'])) {
                    return $r;
                } else {
                    return $this->_compareSubType($a['type'], $b['type']);
                }
            } else {
                return $a_count < $b_count;
            }
        } else {
            return $a_q < $b_q;
        }
    }

    /**
     * Compare two subtypes
     *
     * @param string $a First subtype to compare
     * @param string $b Second subtype to compare
     * @return int
     */
    private function _compareSubType($a, $b)
    {
        if ($a === '*' && $b !== '*') {
            return 1;
        } elseif ($b === '*' && $a !== '*') {
            return -1;
        } else {
            return 0;
        }
    }
}

repo_name: suraj/sloth

license: mit


code:
<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Database independent query interface
 *
 * PHP version 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-2007 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 (?) placeholder was used
 *
 * Quote and escape the value as necessary.
 */
define('DB_PARAM_SCALAR', 1);

/**
 * Indicates an opaque (&) 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 (!) 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 |
 * and removed using ^.
 *
 * @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 |
 * and removed using ^.
 *
 * @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):
 *

 * 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.
 *

 *
 * @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-2007 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: 1.9.2
 * @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()
     */
    public static function factory($type, $options = [])
    {
        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()

    /**
     * Determines if a variable is a DB_Error object
     *
     * @param mixed $value the variable to check
     *
     * @return bool  whether $value is DB_Error object
     */
    public static function isError($value)
    {
        return is_object($value) && is_a($value, 'DB_Error');
    }

    // }}}
    // {{{ apiVersion()

    /**
     * Create a new DB object including a connection to the specified database
     *
     * Example 1.
     *
     * 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());
     * }
     *
     *
     * @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()
     */
    public static 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 '"
                . DB::getDSNString($dsn, true) . "'",
                '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)) {
            if (is_array($dsn)) {
                $err->addUserInfo(DB::getDSNString($dsn, true));
            } else {
                $err->addUserInfo($dsn);
            }
            return $err;
        }

        return $obj;
    }

    // }}}
    // {{{ isError()

    /**
     * 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:
     *
     *  phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
     *
     *
     * Most variations are allowed:
     *
     *  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
     *
     *
     * @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
     */
    public static 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 (empty($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 (strpos($proto_opts, ':') !== false) {
            list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
        }
        if ($parsed['protocol'] == 'tcp') {
            $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;
    }

    // }}}
    // {{{ isConnection()

    /**
     * Returns the given DSN in a string format suitable for output.
     *
     * @param array|string the DSN to parse and format
     * @param boolean true to hide the password, false to include it
     * @return string
     */
    public static function getDSNString($dsn, $hidePassword)
    {
        /* Calling parseDSN will ensure that we have all the array elements
         * defined, and means that we deal with strings and array in the same
         * manner. */
        $dsnArray = DB::parseDSN($dsn);

        if ($hidePassword) {
            $dsnArray['password'] = 'PASSWORD';
        }

        /* Protocol is special-cased, as using the default "tcp" along with an
         * Oracle TNS connection string fails. */
        if (is_string($dsn) && strpos($dsn, 'tcp') === false && $dsnArray['protocol'] == 'tcp') {
            $dsnArray['protocol'] = false;
        }

        // Now we just have to construct the actual string. This is ugly.
        $dsnString = $dsnArray['phptype'];
        if ($dsnArray['dbsyntax']) {
            $dsnString .= '(' . $dsnArray['dbsyntax'] . ')';
        }
        $dsnString .= '://'
            . $dsnArray['username']
            . ':'
            . $dsnArray['password']
            . '@'
            . $dsnArray['protocol'];
        if ($dsnArray['socket']) {
            $dsnString .= '(' . $dsnArray['socket'] . ')';
        }
        if ($dsnArray['protocol'] && $dsnArray['hostspec']) {
            $dsnString .= '+';
        }
        $dsnString .= $dsnArray['hostspec'];
        if ($dsnArray['port']) {
            $dsnString .= ':' . $dsnArray['port'];
        }
        $dsnString .= '/' . $dsnArray['database'];

        /* Option handling. Unfortunately, parseDSN simply places options into
         * the top-level array, so we'll first get rid of the fields defined by
         * DB and see what's left. */
        unset($dsnArray['phptype'],
            $dsnArray['dbsyntax'],
            $dsnArray['username'],
            $dsnArray['password'],
            $dsnArray['protocol'],
            $dsnArray['socket'],
            $dsnArray['hostspec'],
            $dsnArray['port'],
            $dsnArray['database']
        );
        if (count($dsnArray) > 0) {
            $dsnString .= '?';
            $i = 0;
            foreach ($dsnArray as $key => $value) {
                if (++$i > 1) {
                    $dsnString .= '&';
                }
                $dsnString .= $key . '=' . $value;
            }
        }

        return $dsnString;
    }

    // }}}
    // {{{ isManip()

    /**
     * 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
     */
    public static function isConnection($value)
    {
        return (is_object($value) &&
            is_subclass_of($value, 'db_common') &&
            method_exists($value, 'simpleQuery'));
    }

    // }}}
    // {{{ errorMessage()

    /**
     * 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
     */
    public static function isManip($query)
    {
        $manips = 'INSERT|UPDATE|DELETE|REPLACE|'
            . 'CREATE|DROP|'
            . 'LOAD DATA|SELECT .* INTO .* FROM|COPY|'
            . 'ALTER|GRANT|REVOKE|'
            . 'LOCK|UNLOCK';
        if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) {
            return true;
        }
        return false;
    }

    // }}}
    // {{{ parseDSN()

    /**
     * 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
     */
    public static 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];
    }

    // }}}
    // {{{ getDSNString()

    /**
     * Return the DB API version
     *
     * @return string  the DB API version number
     */
    function apiVersion()
    {
        return '1.9.2';
    }

    // }}}
}

// }}}
// {{{ 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-2007 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: 1.9.2
 * @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 __construct($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
                         $level = E_USER_NOTICE, $debuginfo = null)
    {
        if (is_int($code)) {
            parent::__construct('DB Error: ' . DB::errorMessage($code), $code,
                $mode, $level, $debuginfo);
        } else {
            parent::__construct("DB Error: $code", DB_ERROR,
                $mode, $level, $debuginfo);
        }
    }

    /**
     * Workaround to both avoid the "Redefining already defined constructor"
     * PHP error and provide backward compatibility in case someone is calling
     * DB_Error() dynamically
     * @param $method
     * @param $arguments
     * @return bool|mixed
     */
    public function __call($method, $arguments)
    {
        if ($method == 'DB_Error') {
            return call_user_func_array(array($this, '__construct'), $arguments);
        }
        trigger_error(
            'Call to undefined method DB_Error::' . $method . '()', E_USER_ERROR
        );
        return false;
    }
    // }}}
}

// }}}
// {{{ 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-2007 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: 1.9.2
 * @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 __construct(&$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 (is_null($rownum) && $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()

    /**
     * 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;
    }

    // }}}
    // {{{ numCols()

    /**
     * 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 (is_null($rownum) && $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;
    }

    // }}}
    // {{{ numRows()

    /**
     * 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);
    }

    // }}}
    // {{{ nextResult()

    /**
     * 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++;
            }
            $count = $i;
        } else {
            $count = $this->dbh->numRows($this->result);
        }

        /* fbsql is checked for here because limit queries are implemented
         * using a TOP() function, which results in fbsql_num_rows still
         * returning the total number of rows that would have been returned,
         * rather than the real number. As a result, we'll just do the limit
         * calculations for fbsql in the same way as a database with emulated
         * limits. Unfortunately, we can't just do this in DB_fbsql::numRows()
         * because that only gets the result resource, rather than the full
         * DB_Result object. */
        if (($this->dbh->features['limit'] === 'emulate'
                && $this->limit_from !== null)
            || $this->dbh->phptype == 'fbsql') {
            $limit_count = is_null($this->limit_count) ? $count : $this->limit_count;
            if ($count < $this->limit_from) {
                $count = 0;
            } elseif ($count < ($this->limit_from + $limit_count)) {
                $count -= $this->limit_from;
            } else {
                $count = $limit_count;
            }
        }

        return $count;
    }

    // }}}
    // {{{ free()

    /**
     * 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);
    }

    // }}}
    // {{{ tableInfo()

    /**
     * @param null $mode
     * @return
     * @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-2007 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: 1.9.2
 * @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 __construct(&$arr)
    {
        foreach ($arr as $key => $value) {
            $this->$key = &$arr[$key];
        }
    }

    // }}}
}

// }}}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 */