code:
<?php
// vim: foldmethod=marker

/* Generic exception class
 */
if (!class_exists('OAuthException')) {
  class OAuthException extends Exception {
    // pass
  }
}

class OAuthConsumer {
  public $key;
  public $secret;

  function __construct($key, $secret, $callback_url=NULL) {
    $this->key = $key;
    $this->secret = $secret;
    $this->callback_url = $callback_url;
  }

  function __toString() {
    return "OAuthConsumer[key=$this->key,secret=$this->secret]";
  }
}

class OAuthToken {
  // access tokens and request tokens
  public $key;
  public $secret;

  /**
   * key = the token
   * secret = the token secret
   */
  function __construct($key, $secret) {
    $this->key = $key;
    $this->secret = $secret;
  }

  /**
   * generates the basic string serialization of a token that a server
   * would respond to request_token and access_token calls with
   */
  function to_string() {
    return "oauth_token=" .
           OAuthUtil::urlencode_rfc3986($this->key) .
           "&oauth_token_secret=" .
           OAuthUtil::urlencode_rfc3986($this->secret);
  }

  function __toString() {
    return $this->to_string();
  }
}

/**
 * A class for implementing a Signature Method
 * See section 9 ("Signing Requests") in the spec
 */
abstract class OAuthSignatureMethod {
  /**
   * Needs to return the name of the Signature Method (ie HMAC-SHA1)
   * @return string
   */
  abstract public function get_name();

  /**
   * Build up the signature
   * NOTE: The output of this function MUST NOT be urlencoded.
   * the encoding is handled in OAuthRequest when the final
   * request is serialized
   * @param OAuthRequest $request
   * @param OAuthConsumer $consumer
   * @param OAuthToken $token
   * @return string
   */
  abstract public function build_signature($request, $consumer, $token);

  /**
   * Verifies that a given signature is correct
   * @param OAuthRequest $request
   * @param OAuthConsumer $consumer
   * @param OAuthToken $token
   * @param string $signature
   * @return bool
   */
  public function check_signature($request, $consumer, $token, $signature) {
    $built = $this->build_signature($request, $consumer, $token);

    // Check for zero length, although unlikely here
    if (strlen($built) == 0 || strlen($signature) == 0) {
      return false;
    }

    if (strlen($built) != strlen($signature)) {
      return false;
    }

    // Avoid a timing leak with a (hopefully) time insensitive compare
    $result = 0;
    for ($i = 0; $i < strlen($signature); $i++) {
      $result |= ord($built{$i}) ^ ord($signature{$i});
    }

    return $result == 0;
  }
}

/**
 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
 * where the Signature Base String is the text and the key is the concatenated values (each first
 * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
 * character (ASCII code 38) even if empty.
 *   - Chapter 9.2 ("HMAC-SHA1")
 */
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
  function get_name() {
    return "HMAC-SHA1";
  }

  public function build_signature($request, $consumer, $token) {
    $base_string = $request->get_signature_base_string();
    $request->base_string = $base_string;

    $key_parts = array(
      $consumer->secret,
      ($token) ? $token->secret : ""
    );

    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
    $key = implode('&', $key_parts);

    return base64_encode(hash_hmac('sha1', $base_string, $key, true));
  }
}

/**
 * The PLAINTEXT method does not provide any security protection and SHOULD only be used
 * over a secure channel such as HTTPS. It does not use the Signature Base String.
 *   - Chapter 9.4 ("PLAINTEXT")
 */
class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
  public function get_name() {
    return "PLAINTEXT";
  }

  /**
   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
   * empty. The result MUST be encoded again.
   *   - Chapter 9.4.1 ("Generating Signatures")
   *
   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
   * OAuthRequest handles this!
   */
  public function build_signature($request, $consumer, $token) {
    $key_parts = array(
      $consumer->secret,
      ($token) ? $token->secret : ""
    );

    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
    $key = implode('&', $key_parts);
    $request->base_string = $key;

    return $key;
  }
}

/**
 * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
 * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
 * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
 * verified way to the Service Provider, in a manner which is beyond the scope of this
 * specification.
 *   - Chapter 9.3 ("RSA-SHA1")
 */
abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
  public function get_name() {
    return "RSA-SHA1";
  }

  // Up to the SP to implement this lookup of keys. Possible ideas are:
  // (1) do a lookup in a table of trusted certs keyed off of consumer
  // (2) fetch via http using a url provided by the requester
  // (3) some sort of specific discovery code based on request
  //
  // Either way should return a string representation of the certificate
  protected abstract function fetch_public_cert(&$request);

  // Up to the SP to implement this lookup of keys. Possible ideas are:
  // (1) do a lookup in a table of trusted certs keyed off of consumer
  //
  // Either way should return a string representation of the certificate
  protected abstract function fetch_private_cert(&$request);

  public function build_signature($request, $consumer, $token) {
    $base_string = $request->get_signature_base_string();
    $request->base_string = $base_string;

    // Fetch the private key cert based on the request
    $cert = $this->fetch_private_cert($request);

    // Pull the private key ID from the certificate
    $privatekeyid = openssl_get_privatekey($cert);

    // Sign using the key
    $ok = openssl_sign($base_string, $signature, $privatekeyid);

    // Release the key resource
    openssl_free_key($privatekeyid);

    return base64_encode($signature);
  }

  public function check_signature($request, $consumer, $token, $signature) {
    $decoded_sig = base64_decode($signature);

    $base_string = $request->get_signature_base_string();

    // Fetch the public key cert based on the request
    $cert = $this->fetch_public_cert($request);

    // Pull the public key ID from the certificate
    $publickeyid = openssl_get_publickey($cert);

    // Check the computed signature against the one passed in the query
    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);

    // Release the key resource
    openssl_free_key($publickeyid);

    return $ok == 1;
  }
}

class OAuthRequest {
  protected $parameters;
  protected $http_method;
  protected $http_url;
  // for debug purposes
  public $base_string;
  public static $version = '1.0';
  public static $POST_INPUT = 'php://input';

  function __construct($http_method, $http_url, $parameters=NULL) {
    $parameters = ($parameters) ? $parameters : array();
    $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
    $this->parameters = $parameters;
    $this->http_method = $http_method;
    $this->http_url = $http_url;
  }

  /**
   * attempt to build up a request from what was passed to the server
   */
  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
              ? 'http'
              : 'https';
    $http_url = ($http_url) ? $http_url : $scheme .
                              '://' . $_SERVER['SERVER_NAME'] .
                              ':' .
                              $_SERVER['SERVER_PORT'] .
                              $_SERVER['REQUEST_URI'];
    $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];

    // We weren't handed any parameters, so let's find the ones relevant to
    // this request.
    // If you run XML-RPC or similar you should use this to provide your own
    // parsed parameter-list
    if (!$parameters) {
      // Find request headers
      $request_headers = OAuthUtil::get_headers();

      // Parse the query-string to find GET parameters
      $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);

      // It's a POST request of the proper content-type, so parse POST
      // parameters and add those overriding any duplicates from GET
      if ($http_method == "POST"
          &&  isset($request_headers['Content-Type'])
          && strstr($request_headers['Content-Type'],
                     'application/x-www-form-urlencoded')
          ) {
        $post_data = OAuthUtil::parse_parameters(
          file_get_contents(self::$POST_INPUT)
        );
        $parameters = array_merge($parameters, $post_data);
      }

      // We have a Authorization-header with OAuth data. Parse the header
      // and add those overriding any duplicates from GET or POST
      if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
        $header_parameters = OAuthUtil::split_header(
          $request_headers['Authorization']
        );
        $parameters = array_merge($parameters, $header_parameters);
      }

    }

    return new OAuthRequest($http_method, $http_url, $parameters);
  }

  /**
   * pretty much a helper function to set up the request
   */
  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
    $parameters = ($parameters) ?  $parameters : array();
    $defaults = array("oauth_version" => OAuthRequest::$version,
                      "oauth_nonce" => OAuthRequest::generate_nonce(),
                      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
                      "oauth_consumer_key" => $consumer->key);
    if ($token)
      $defaults['oauth_token'] = $token->key;

    $parameters = array_merge($defaults, $parameters);

    return new OAuthRequest($http_method, $http_url, $parameters);
  }

  public function set_parameter($name, $value, $allow_duplicates = true) {
    if ($allow_duplicates && isset($this->parameters[$name])) {
      // We have already added parameter(s) with this name, so add to the list
      if (is_scalar($this->parameters[$name])) {
        // This is the first duplicate, so transform scalar (string)
        // into an array so we can add the duplicates
        $this->parameters[$name] = array($this->parameters[$name]);
      }

      $this->parameters[$name][] = $value;
    } else {
      $this->parameters[$name] = $value;
    }
  }

  public function get_parameter($name) {
    return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
  }

  public function get_parameters() {
    return $this->parameters;
  }

  public function unset_parameter($name) {
    unset($this->parameters[$name]);
  }

  /**
   * The request parameters, sorted and concatenated into a normalized string.
   * @return string
   */
  public function get_signable_parameters() {
    // Grab all parameters
    $params = $this->parameters;

    // Remove oauth_signature if present
    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
    if (isset($params['oauth_signature'])) {
      unset($params['oauth_signature']);
    }

    return OAuthUtil::build_http_query($params);
  }

  /**
   * Returns the base string of this request
   *
   * The base string defined as the method, the url
   * and the parameters (normalized), each urlencoded
   * and the concated with &.
   */
  public function get_signature_base_string() {
    $parts = array(
      $this->get_normalized_http_method(),
      $this->get_normalized_http_url(),
      $this->get_signable_parameters()
    );

    $parts = OAuthUtil::urlencode_rfc3986($parts);

    return implode('&', $parts);
  }

  /**
   * just uppercases the http method
   */
  public function get_normalized_http_method() {
    return strtoupper($this->http_method);
  }

  /**
   * parses the url and rebuilds it to be
   * scheme://host/path
   */
  public function get_normalized_http_url() {
    $parts = parse_url($this->http_url);

    $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
    $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
    $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
    $path = (isset($parts['path'])) ? $parts['path'] : '';

    if (($scheme == 'https' && $port != '443')
        || ($scheme == 'http' && $port != '80')) {
      $host = "$host:$port";
    }
    return "$scheme://$host$path";
  }

  /**
   * builds a url usable for a GET request
   */
  public function to_url() {
    $post_data = $this->to_postdata();
    $out = $this->get_normalized_http_url();
    if ($post_data) {
      $out .= '?'.$post_data;
    }
    return $out;
  }

  /**
   * builds the data one would send in a POST request
   */
  public function to_postdata() {
    return OAuthUtil::build_http_query($this->parameters);
  }

  /**
   * builds the Authorization: header
   */
  public function to_header($realm=null) {
    $first = true;
	if($realm) {
      $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
      $first = false;
    } else
      $out = 'Authorization: OAuth';

    $total = array();
    foreach ($this->parameters as $k => $v) {
      if (substr($k, 0, 5) != "oauth") continue;
      if (is_array($v)) {
        throw new OAuthException('Arrays not supported in headers');
      }
      $out .= ($first) ? ' ' : ',';
      $out .= OAuthUtil::urlencode_rfc3986($k) .
              '="' .
              OAuthUtil::urlencode_rfc3986($v) .
              '"';
      $first = false;
    }
    return $out;
  }

  public function __toString() {
    return $this->to_url();
  }

  public function sign_request($signature_method, $consumer, $token) {
    $this->set_parameter(
      "oauth_signature_method",
      $signature_method->get_name(),
      false
    );
    $signature = $this->build_signature($signature_method, $consumer, $token);
    $this->set_parameter("oauth_signature", $signature, false);
  }

  public function build_signature($signature_method, $consumer, $token) {
    $signature = $signature_method->build_signature($this, $consumer, $token);
    return $signature;
  }

  /**
   * util function: current timestamp
   */
  private static function generate_timestamp() {
    return time();
  }

  /**
   * util function: current nonce
   */
  private static function generate_nonce() {
    $mt = microtime();
    $rand = mt_rand();

    return md5($mt . $rand); // md5s look nicer than numbers
  }
}

class OAuthServer {
  protected $timestamp_threshold = 300; // in seconds, five minutes
  protected $version = '1.0';             // hi blaine
  protected $signature_methods = array();

  protected $data_store;

  function __construct($data_store) {
    $this->data_store = $data_store;
  }

  public function add_signature_method($signature_method) {
    $this->signature_methods[$signature_method->get_name()] =
      $signature_method;
  }

  // high level functions

  /**
   * process a request_token request
   * returns the request token on success
   */
  public function fetch_request_token(&$request) {
    $this->get_version($request);

    $consumer = $this->get_consumer($request);

    // no token required for the initial token request
    $token = NULL;

    $this->check_signature($request, $consumer, $token);

    // Rev A change
    $callback = $request->get_parameter('oauth_callback');
    $new_token = $this->data_store->new_request_token($consumer, $callback);

    return $new_token;
  }

  /**
   * process an access_token request
   * returns the access token on success
   */
  public function fetch_access_token(&$request) {
    $this->get_version($request);

    $consumer = $this->get_consumer($request);

    // requires authorized request token
    $token = $this->get_token($request, $consumer, "request");

    $this->check_signature($request, $consumer, $token);

    // Rev A change
    $verifier = $request->get_parameter('oauth_verifier');
    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);

    return $new_token;
  }

  /**
   * verify an api call, checks all the parameters
   */
  public function verify_request(&$request) {
    $this->get_version($request);
    $consumer = $this->get_consumer($request);
    $token = $this->get_token($request, $consumer, "access");
    $this->check_signature($request, $consumer, $token);
    return array($consumer, $token);
  }

  // Internals from here
  /**
   * version 1
   */
  private function get_version(&$request) {
    $version = $request->get_parameter("oauth_version");
    if (!$version) {
      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
      // Chapter 7.0 ("Accessing Protected Ressources")
      $version = '1.0';
    }
    if ($version !== $this->version) {
      throw new OAuthException("OAuth version '$version' not supported");
    }
    return $version;
  }

  /**
   * figure out the signature with some defaults
   */
  private function get_signature_method($request) {
    $signature_method = $request instanceof OAuthRequest
        ? $request->get_parameter("oauth_signature_method")
        : NULL;

    if (!$signature_method) {
      // According to chapter 7 ("Accessing Protected Ressources") the signature-method
      // parameter is required, and we can't just fallback to PLAINTEXT
      throw new OAuthException('No signature method parameter. This parameter is required');
    }

    if (!in_array($signature_method,
                  array_keys($this->signature_methods))) {
      throw new OAuthException(
        "Signature method '$signature_method' not supported " .
        "try one of the following: " .
        implode(", ", array_keys($this->signature_methods))
      );
    }
    return $this->signature_methods[$signature_method];
  }

  /**
   * try to find the consumer for the provided request's consumer key
   */
  private function get_consumer($request) {
    $consumer_key = $request instanceof OAuthRequest
        ? $request->get_parameter("oauth_consumer_key")
        : NULL;

    if (!$consumer_key) {
      throw new OAuthException("Invalid consumer key");
    }

    $consumer = $this->data_store->lookup_consumer($consumer_key);
    if (!$consumer) {
      throw new OAuthException("Invalid consumer");
    }

    return $consumer;
  }

  /**
   * try to find the token for the provided request's token key
   */
  private function get_token($request, $consumer, $token_type="access") {
    $token_field = $request instanceof OAuthRequest
         ? $request->get_parameter('oauth_token')
         : NULL;

    $token = $this->data_store->lookup_token(
      $consumer, $token_type, $token_field
    );
    if (!$token) {
      throw new OAuthException("Invalid $token_type token: $token_field");
    }
    return $token;
  }

  /**
   * all-in-one function to check the signature on a request
   * should guess the signature method appropriately
   */
  private function check_signature($request, $consumer, $token) {
    // this should probably be in a different method
    $timestamp = $request instanceof OAuthRequest
        ? $request->get_parameter('oauth_timestamp')
        : NULL;
    $nonce = $request instanceof OAuthRequest
        ? $request->get_parameter('oauth_nonce')
        : NULL;

    $this->check_timestamp($timestamp);
    $this->check_nonce($consumer, $token, $nonce, $timestamp);

    $signature_method = $this->get_signature_method($request);

    $signature = $request->get_parameter('oauth_signature');
    $valid_sig = $signature_method->check_signature(
      $request,
      $consumer,
      $token,
      $signature
    );

    if (!$valid_sig) {
      throw new OAuthException("Invalid signature");
    }
  }

  /**
   * check that the timestamp is new enough
   */
  private function check_timestamp($timestamp) {
    if( ! $timestamp )
      throw new OAuthException(
        'Missing timestamp parameter. The parameter is required'
      );

    // verify that timestamp is recentish
    $now = time();
    if (abs($now - $timestamp) > $this->timestamp_threshold) {
      throw new OAuthException(
        "Expired timestamp, yours $timestamp, ours $now"
      );
    }
  }

  /**
   * check that the nonce is not repeated
   */
  private function check_nonce($consumer, $token, $nonce, $timestamp) {
    if( ! $nonce )
      throw new OAuthException(
        'Missing nonce parameter. The parameter is required'
      );

    // verify that the nonce is uniqueish
    $found = $this->data_store->lookup_nonce(
      $consumer,
      $token,
      $nonce,
      $timestamp
    );
    if ($found) {
      throw new OAuthException("Nonce already used: $nonce");
    }
  }

}

class OAuthDataStore {
  function lookup_consumer($consumer_key) {
    // implement me
  }

  function lookup_token($consumer, $token_type, $token) {
    // implement me
  }

  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
    // implement me
  }

  function new_request_token($consumer, $callback = null) {
    // return a new token attached to this consumer
  }

  function new_access_token($token, $consumer, $verifier = null) {
    // return a new access token attached to this consumer
    // for the user associated with this token if the request token
    // is authorized
    // should also invalidate the request token
  }

}

class OAuthUtil {
  public static function urlencode_rfc3986($input) {
  if (is_array($input)) {
    return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
  } else if (is_scalar($input)) {
    return str_replace(
      '+',
      ' ',
      str_replace('%7E', '~', rawurlencode($input))
    );
  } else {
    return '';
  }
}

  // This decode function isn't taking into consideration the above
  // modifications to the encoding process. However, this method doesn't
  // seem to be used anywhere so leaving it as is.
  public static function urldecode_rfc3986($string) {
    return urldecode($string);
  }

  // Utility function for turning the Authorization: header into
  // parameters, has to do some unescaping
  // Can filter out any non-oauth parameters if needed (default behaviour)
  // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
  //                  see http://code.google.com/p/oauth/issues/detail?id=163
  public static function split_header($header, $only_allow_oauth_parameters = true) {
    $params = array();
    if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
      foreach ($matches[1] as $i => $h) {
        $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
      }
      if (isset($params['realm'])) {
        unset($params['realm']);
      }
    }
    return $params;
  }

  // helper to try to sort out headers for people who aren't running apache
  public static function get_headers() {
    if (function_exists('apache_request_headers')) {
      // we need this to get the actual Authorization: header
      // because apache tends to tell us it doesn't exist
      $headers = apache_request_headers();

      // sanitize the output of apache_request_headers because
      // we always want the keys to be Cased-Like-This and arh()
      // returns the headers in the same case as they are in the
      // request
      $out = array();
      foreach ($headers AS $key => $value) {
        $key = str_replace(
            " ",
            "-",
            ucwords(strtolower(str_replace("-", " ", $key)))
          );
        $out[$key] = $value;
      }
    } else {
      // otherwise we don't have apache and are just going to have to hope
      // that $_SERVER actually contains what we need
      $out = array();
      if( isset($_SERVER['CONTENT_TYPE']) )
        $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
      if( isset($_ENV['CONTENT_TYPE']) )
        $out['Content-Type'] = $_ENV['CONTENT_TYPE'];

      foreach ($_SERVER as $key => $value) {
        if (substr($key, 0, 5) == "HTTP_") {
          // this is chaos, basically it is just there to capitalize the first
          // letter of every word that is not an initial HTTP and strip HTTP
          // code from przemek
          $key = str_replace(
            " ",
            "-",
            ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
          );
          $out[$key] = $value;
        }
      }
    }
    return $out;
  }

  // This function takes a input like a=b&a=c&d=e and returns the parsed
  // parameters like this
  // array('a' => array('b','c'), 'd' => 'e')
  public static function parse_parameters( $input ) {
    if (!isset($input) || !$input) return array();

    $pairs = explode('&', $input);

    $parsed_parameters = array();
    foreach ($pairs as $pair) {
      $split = explode('=', $pair, 2);
      $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
      $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';

      if (isset($parsed_parameters[$parameter])) {
        // We have already recieved parameter(s) with this name, so add to the list
        // of parameters with this name

        if (is_scalar($parsed_parameters[$parameter])) {
          // This is the first duplicate, so transform scalar (string) into an array
          // so we can add the duplicates
          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
        }

        $parsed_parameters[$parameter][] = $value;
      } else {
        $parsed_parameters[$parameter] = $value;
      }
    }
    return $parsed_parameters;
  }

  public static function build_http_query($params) {
    if (!$params) return '';

    // Urlencode both keys and values
    $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
    $values = OAuthUtil::urlencode_rfc3986(array_values($params));
    $params = array_combine($keys, $values);

    // Parameters are sorted by name, using lexicographical byte value ordering.
    // Ref: Spec: 9.1.1 (1)
    uksort($params, 'strcmp');

    $pairs = array();
    foreach ($params as $parameter => $value) {
      if (is_array($value)) {
        // If two or more parameters share the same name, they are sorted by their value
        // Ref: Spec: 9.1.1 (1)
        // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
        sort($value, SORT_STRING);
        foreach ($value as $duplicate_value) {
          $pairs[] = $parameter . '=' . $duplicate_value;
        }
      } else {
        $pairs[] = $parameter . '=' . $value;
      }
    }
    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
    // Each name-value pair is separated by an '&' character (ASCII code 38)
    return implode('&', $pairs);
  }
}

?>


code:
The MIT License

Copyright (c) 2007 Andy Smith

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

repo_name: dansup/gnu

license: mit


code:
<?php
/**
 * PEAR, the PHP Extension and Application Repository
 *
 * PEAR class and PEAR_Error class
 *
 * PHP versions 4 and 5
 *
 * @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-2010 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    CVS: $Id: PEAR.php 313023 2011-07-06 19:17:11Z dufuz $
 * @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
}

$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://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: 1.9.4
 * @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
{
    /**
     * 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.  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 (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)));
        }
    }

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

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

    /**
     * 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')) {
            return false;
        }

        if (is_null($code)) {
            return true;
        } elseif (is_string($code)) {
            return $data->getMessage() == $code;
        }

        return $data->getCode() == $code;
    }

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

    /**
     * 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 count($this->_expected_errors);
    }

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

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

    /**
     * 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) {
                $deleted =  $this->_checkDelExpect($error) ? true : 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;
            }

            return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
        }

        // $error_code is empty
        return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
    }

    /**
     * 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) &&
            count($this->_expected_errors) > 0 &&
            count($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 (intval(PHP_VERSION) < 5) {
            // little non-eval hack to fix bug #12147
            include 'PEAR/FixPHP5PEARWarnings.php';
            return $a;
        }

        if ($skipmsg) {
            $a = new $ec($code, $mode, $options, $userinfo);
        } else {
            $a = new $ec($message, $code, $mode, $options, $userinfo);
        }

        return $a;
    }

    /**
     * Simpler form of raiseError with fewer options.  In most cases
     * message, code and userinfo are enough.
     *
     * @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 string $userinfo If you need to pass along for example debug
     *                  information, this parameter is meant for that.
     *
     * @access public
     * @return object   a PEAR error object
     * @see PEAR::raiseError
     */
    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;
        }

        $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;
    }

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

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

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

        // if either returns true dl() will produce a FATAL error, stop that
        if (
            function_exists('dl') === false ||
            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);
    }
}

if (PEAR_ZE2) {
    include_once 'PEAR5.php';
}

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_ZE2) {
            $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo');
        } else {
            $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
        }

        if ($destructLifoExists) {
            $_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 (
        isset($GLOBALS['_PEAR_shutdown_funcs']) &&
        is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
        !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://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: 1.9.4
 * @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
{
    var $error_message_prefix = '';
    var $mode                 = PEAR_ERROR_RETURN;
    var $level                = E_USER_NOTICE;
    var $code                 = -1;
    var $message              = '';
    var $userinfo             = '';
    var $backtrace            = null;

    /**
     * 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_ZE2) {
            $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
        } else {
            $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
        }

        if (!$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 && 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);');
        }
    }

    /**
     * Get the error mode from an error object.
     *
     * @return int error mode
     * @access public
     */
    function getMode()
    {
        return $this->mode;
    }

    /**
     * Get the callback function/method from an error object.
     *
     * @return mixed callback function or object/method array
     * @access public
     */
    function getCallback()
    {
        return $this->callback;
    }

    /**
     * Get the error message from an error object.
     *
     * @return  string  full error message
     * @access public
     */
    function getMessage()
    {
        return ($this->error_message_prefix . $this->message);
    }

    /**
     * Get error code from an error object
     *
     * @return int error code
     * @access public
     */
     function getCode()
     {
        return $this->code;
     }

    /**
     * Get the name of this error/exception.
     *
     * @return string error/exception name (type)
     * @access public
     */
    function getType()
    {
        return get_class($this);
    }

    /**
     * Get additional user-supplied information.
     *
     * @return string user-supplied information
     * @access public
     */
    function getUserInfo()
    {
        return $this->userinfo;
    }

    /**
     * Get additional debug information supplied by the application.
     *
     * @return string debug information
     * @access public
     */
    function getDebugInfo()
    {
        return $this->getUserInfo();
    }

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

    function addUserInfo($info)
    {
        if (empty($this->userinfo)) {
            $this->userinfo = $info;
        } else {
            $this->userinfo .= " ** $info";
        }
    }

    function __toString()
    {
        return $this->getMessage();
    }

    /**
     * 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:
 */


code:
<?php
/**
 * Error Stack Implementation
 *
 * This is an incredibly simple implementation of a very complex error handling
 * facility.  It contains the ability
 * to track multiple errors from multiple packages simultaneously.  In addition,
 * it can track errors of many levels, save data along with the error, context
 * information such as the exact file, line number, class and function that
 * generated the error, and if necessary, it can raise a traditional PEAR_Error.
 * It has built-in support for PEAR::Log, to log errors as they occur
 *
 * Since version 0.2alpha, it is also possible to selectively ignore errors,
 * through the use of an error callback, see {@link pushCallback()}
 *
 * Since version 0.3alpha, it is possible to specify the exception class
 * returned from {@link push()}
 *
 * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class.  This can
 * still be done quite handily in an error callback or by manipulating the returned array
 * @category   Debugging
 * @package    PEAR_ErrorStack
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  2004-2008 Greg Beaver
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $
 * @link       http://pear.php.net/package/PEAR_ErrorStack
 */

/**
 * Singleton storage
 *
 * Format:
 *

 * array(
 *  'package1' => PEAR_ErrorStack object,
 *  'package2' => PEAR_ErrorStack object,
 *  ...
 * )
 *

 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
 */
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();

/**
 * Global error callback (default)
 *
 * This is only used if set to non-false.  * is the default callback for
 * all packages, whereas specific packages may set a default callback
 * for all instances, regardless of whether they are a singleton or not.
 *
 * To exclude non-singletons, only set the local callback for the singleton
 * @see PEAR_ErrorStack::setDefaultCallback()
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
 */
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
    '*' => false,
);

/**
 * Global Log object (default)
 *
 * This is only used if set to non-false.  Use to set a default log object for
 * all stacks, regardless of instantiation order or location
 * @see PEAR_ErrorStack::setDefaultLogger()
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
 */
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;

/**
 * Global Overriding Callback
 *
 * This callback will override any error callbacks that specific loggers have set.
 * Use with EXTREME caution
 * @see PEAR_ErrorStack::staticPushCallback()
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
 */
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();

/**#@+
 * One of four possible return values from the error Callback
 * @see PEAR_ErrorStack::_errorCallback()
 */
/**
 * If this is returned, then the error will be both pushed onto the stack
 * and logged.
 */
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
 * If this is returned, then the error will only be pushed onto the stack,
 * and not logged.
 */
define('PEAR_ERRORSTACK_PUSH', 2);
/**
 * If this is returned, then the error will only be logged, but not pushed
 * onto the error stack.
 */
define('PEAR_ERRORSTACK_LOG', 3);
/**
 * If this is returned, then the error is completely ignored.
 */
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
 * If this is returned, then the error is logged and die() is called.
 */
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/

/**
 * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
 * the singleton method.
 */
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);

/**
 * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
 * that has no __toString() method
 */
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
 * Error Stack Implementation
 *
 * Usage:
 *
 * // global error stack
 * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
 * // local error stack
 * $local_stack = new PEAR_ErrorStack('MyPackage');
 *
 * @author     Greg Beaver <cellog@php.net>
 * @version    1.9.4
 * @package    PEAR_ErrorStack
 * @category   Debugging
 * @copyright  2004-2008 Greg Beaver
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $
 * @link       http://pear.php.net/package/PEAR_ErrorStack
 */
class PEAR_ErrorStack {
    /**
     * Errors are stored in the order that they are pushed on the stack.
     * @since 0.4alpha Errors are no longer organized by error level.
     * This renders pop() nearly unusable, and levels could be more easily
     * handled in a callback anyway
     * @var array
     * @access private
     */
    var $_errors = array();

    /**
     * Storage of errors by level.
     *
     * Allows easy retrieval and deletion of only errors from a particular level
     * @since PEAR 1.4.0dev
     * @var array
     * @access private
     */
    var $_errorsByLevel = array();

    /**
     * Package name this error stack represents
     * @var string
     * @access protected
     */
    var $_package;

    /**
     * Determines whether a PEAR_Error is thrown upon every error addition
     * @var boolean
     * @access private
     */
    var $_compat = false;

    /**
     * If set to a valid callback, this will be used to generate the error
     * message from the error code, otherwise the message passed in will be
     * used
     * @var false|string|array
     * @access private
     */
    var $_msgCallback = false;

    /**
     * If set to a valid callback, this will be used to generate the error
     * context for an error.  For PHP-related errors, this will be a file
     * and line number as retrieved from debug_backtrace(), but can be
     * customized for other purposes.  The error might actually be in a separate
     * configuration file, or in a database query.
     * @var false|string|array
     * @access protected
     */
    var $_contextCallback = false;

    /**
     * If set to a valid callback, this will be called every time an error
     * is pushed onto the stack.  The return value will be used to determine
     * whether to allow an error to be pushed or logged.
     *
     * The return value must be one an PEAR_ERRORSTACK_* constant
     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
     * @var false|string|array
     * @access protected
     */
    var $_errorCallback = array();

    /**
     * PEAR::Log object for logging errors
     * @var false|Log
     * @access protected
     */
    var $_logger = false;

    /**
     * Error messages - designed to be overridden
     * @var array
     * @abstract
     */
    var $_errorMsgs = array();

    /**
     * Set up a new error stack
     *
     * @param string   $package name of the package this error stack represents
     * @param callback $msgCallback callback used for error message generation
     * @param callback $contextCallback callback used for context generation,
     *                 defaults to {@link getFileLine()}
     * @param boolean  $throwPEAR_Error
     */
    function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false,
                         $throwPEAR_Error = false)
    {
        $this->_package = $package;
        $this->setMessageCallback($msgCallback);
        $this->setContextCallback($contextCallback);
        $this->_compat = $throwPEAR_Error;
    }

    /**
     * Return a single error stack for this package.
     *
     * Note that all parameters are ignored if the stack for package $package
     * has already been instantiated
     * @param string   $package name of the package this error stack represents
     * @param callback $msgCallback callback used for error message generation
     * @param callback $contextCallback callback used for context generation,
     *                 defaults to {@link getFileLine()}
     * @param boolean  $throwPEAR_Error
     * @param string   $stackClass class to instantiate
     * @static
     * @return PEAR_ErrorStack
     */
    function &singleton($package, $msgCallback = false, $contextCallback = false,
                         $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack')
    {
        if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
        }
        if (!class_exists($stackClass)) {
            if (function_exists('debug_backtrace')) {
                $trace = debug_backtrace();
            }
            PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
                'exception', array('stackclass' => $stackClass),
                'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
                false, $trace);
        }
        $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
            new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);

        return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
    }

    /**
     * Internal error handler for PEAR_ErrorStack class
     *
     * Dies if the error is an exception (and would have died anyway)
     * @access private
     */
    function _handleError($err)
    {
        if ($err['level'] == 'exception') {
            $message = $err['message'];
            if (isset($_SERVER['REQUEST_URI'])) {
                echo '
';
            } else {
                echo "\n";
            }
            var_dump($err['context']);
            die($message);
        }
    }

    /**
     * Set up a PEAR::Log object for all error stacks that don't have one
     * @param Log $log
     * @static
     */
    function setDefaultLogger(&$log)
    {
        if (is_object($log) && method_exists($log, 'log') ) {
            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
        } elseif (is_callable($log)) {
            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
	}
    }

    /**
     * Set up a PEAR::Log object for this error stack
     * @param Log $log
     */
    function setLogger(&$log)
    {
        if (is_object($log) && method_exists($log, 'log') ) {
            $this->_logger = &$log;
        } elseif (is_callable($log)) {
            $this->_logger = &$log;
        }
    }

    /**
     * Set an error code => error message mapping callback
     *
     * This method sets the callback that can be used to generate error
     * messages for any instance
     * @param array|string Callback function/method
     */
    function setMessageCallback($msgCallback)
    {
        if (!$msgCallback) {
            $this->_msgCallback = array(&$this, 'getErrorMessage');
        } else {
            if (is_callable($msgCallback)) {
                $this->_msgCallback = $msgCallback;
            }
        }
    }

    /**
     * Get an error code => error message mapping callback
     *
     * This method returns the current callback that can be used to generate error
     * messages
     * @return array|string|false Callback function/method or false if none
     */
    function getMessageCallback()
    {
        return $this->_msgCallback;
    }

    /**
     * Sets a default callback to be used by all error stacks
     *
     * This method sets the callback that can be used to generate error
     * messages for a singleton
     * @param array|string Callback function/method
     * @param string Package name, or false for all packages
     * @static
     */
    function setDefaultCallback($callback = false, $package = false)
    {
        if (!is_callable($callback)) {
            $callback = false;
        }
        $package = $package ? $package : '*';
        $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
    }

    /**
     * Set a callback that generates context information (location of error) for an error stack
     *
     * This method sets the callback that can be used to generate context
     * information for an error.  Passing in NULL will disable context generation
     * and remove the expensive call to debug_backtrace()
     * @param array|string|null Callback function/method
     */
    function setContextCallback($contextCallback)
    {
        if ($contextCallback === null) {
            return $this->_contextCallback = false;
        }
        if (!$contextCallback) {
            $this->_contextCallback = array(&$this, 'getFileLine');
        } else {
            if (is_callable($contextCallback)) {
                $this->_contextCallback = $contextCallback;
            }
        }
    }

    /**
     * Set an error Callback
     * If set to a valid callback, this will be called every time an error
     * is pushed onto the stack.  The return value will be used to determine
     * whether to allow an error to be pushed or logged.
     *
     * The return value must be one of the ERRORSTACK_* constants.
     *
     * This functionality can be used to emulate PEAR's pushErrorHandling, and
     * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
     * the error stack or logging
     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
     * @see popCallback()
     * @param string|array $cb
     */
    function pushCallback($cb)
    {
        array_push($this->_errorCallback, $cb);
    }

    /**
     * Remove a callback from the error callback stack
     * @see pushCallback()
     * @return array|string|false
     */
    function popCallback()
    {
        if (!count($this->_errorCallback)) {
            return false;
        }
        return array_pop($this->_errorCallback);
    }

    /**
     * Set a temporary overriding error callback for every package error stack
     *
     * Use this to temporarily disable all existing callbacks (can be used
     * to emulate the @ operator, for instance)
     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
     * @see staticPopCallback(), pushCallback()
     * @param string|array $cb
     * @static
     */
    function staticPushCallback($cb)
    {
        array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
    }

    /**
     * Remove a temporary overriding error callback
     * @see staticPushCallback()
     * @return array|string|false
     * @static
     */
    function staticPopCallback()
    {
        $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
        if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
            $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
        }
        return $ret;
    }

    /**
     * Add an error to the stack
     *
     * If the message generator exists, it is called with 2 parameters.
     *  - the current Error Stack object
     *  - an array that is in the same format as an error.  Available indices
     *    are 'code', 'package', 'time', 'params', 'level', and 'context'
     *
     * Next, if the error should contain context information, this is
     * handled by the context grabbing method.
     * Finally, the error is pushed onto the proper error stack
     * @param int    $code      Package-specific error code
     * @param string $level     Error level.  This is NOT spell-checked
     * @param array  $params    associative array of error parameters
     * @param string $msg       Error message, or a portion of it if the message
     *                          is to be generated
     * @param array  $repackage If this error re-packages an error pushed by
     *                          another package, place the array returned from
     *                          {@link pop()} in this parameter
     * @param array  $backtrace Protected parameter: use this to pass in the
     *                          {@link debug_backtrace()} that should be used
     *                          to find error context
     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
     * thrown.  If a PEAR_Error is returned, the userinfo
     * property is set to the following array:
     *
     *
     * array(
     *    'code' => $code,
     *    'params' => $params,
     *    'package' => $this->_package,
     *    'level' => $level,
     *    'time' => time(),
     *    'context' => $context,
     *    'message' => $msg,
     * //['repackage' => $err] repackaged error array/Exception class
     * );
     *
     *
     * Normally, the previous array is returned.
     */
    function push($code, $level = 'error', $params = array(), $msg = false,
                  $repackage = false, $backtrace = false)
    {
        $context = false;
        // grab error context
        if ($this->_contextCallback) {
            if (!$backtrace) {
                $backtrace = debug_backtrace();
            }
            $context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
        }

        // save error
        $time = explode(' ', microtime());
        $time = $time[1] + $time[0];
        $err = array(
                'code' => $code,
                'params' => $params,
                'package' => $this->_package,
                'level' => $level,
                'time' => $time,
                'context' => $context,
                'message' => $msg,
               );

        if ($repackage) {
            $err['repackage'] = $repackage;
        }

        // set up the error message, if necessary
        if ($this->_msgCallback) {
            $msg = call_user_func_array($this->_msgCallback,
                                        array(&$this, $err));
            $err['message'] = $msg;
        }
        $push = $log = true;
        $die = false;
        // try the overriding callback first
        $callback = $this->staticPopCallback();
        if ($callback) {
            $this->staticPushCallback($callback);
        }
        if (!is_callable($callback)) {
            // try the local callback next
            $callback = $this->popCallback();
            if (is_callable($callback)) {
                $this->pushCallback($callback);
            } else {
                // try the default callback
                $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
            }
        }
        if (is_callable($callback)) {
            switch(call_user_func($callback, $err)){
            	case PEAR_ERRORSTACK_IGNORE:
            		return $err;
        		break;
            	case PEAR_ERRORSTACK_PUSH:
            		$log = false;
        		break;
            	case PEAR_ERRORSTACK_LOG:
            		$push = false;
        		break;
            	case PEAR_ERRORSTACK_DIE:
            		$die = true;
        		break;
                // anything else returned has the same effect as pushandlog
            }
        }
        if ($push) {
            array_unshift($this->_errors, $err);
            if (!isset($this->_errorsByLevel[$err['level']])) {
                $this->_errorsByLevel[$err['level']] = array();
            }
            $this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
        }
        if ($log) {
            if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
                $this->_log($err);
            }
        }
        if ($die) {
            die();
        }
        if ($this->_compat && $push) {
            return $this->raiseError($msg, $code, null, null, $err);
        }
        return $err;
    }

    /**
     * Static version of {@link push()}
     *
     * @param string $package   Package name this error belongs to
     * @param int    $code      Package-specific error code
     * @param string $level     Error level.  This is NOT spell-checked
     * @param array  $params    associative array of error parameters
     * @param string $msg       Error message, or a portion of it if the message
     *                          is to be generated
     * @param array  $repackage If this error re-packages an error pushed by
     *                          another package, place the array returned from
     *                          {@link pop()} in this parameter
     * @param array  $backtrace Protected parameter: use this to pass in the
     *                          {@link debug_backtrace()} that should be used
     *                          to find error context
     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
     *                          thrown.  see docs for {@link push()}
     * @static
     */
    function staticPush($package, $code, $level = 'error', $params = array(),
                        $msg = false, $repackage = false, $backtrace = false)
    {
        $s = &PEAR_ErrorStack::singleton($package);
        if ($s->_contextCallback) {
            if (!$backtrace) {
                if (function_exists('debug_backtrace')) {
                    $backtrace = debug_backtrace();
                }
            }
        }
        return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
    }

    /**
     * Log an error using PEAR::Log
     * @param array $err Error array
     * @param array $levels Error level => Log constant map
     * @access protected
     */
    function _log($err)
    {
        if ($this->_logger) {
            $logger = &$this->_logger;
        } else {
            $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
        }
        if (is_a($logger, 'Log')) {
            $levels = array(
                'exception' => PEAR_LOG_CRIT,
                'alert' => PEAR_LOG_ALERT,
                'critical' => PEAR_LOG_CRIT,
                'error' => PEAR_LOG_ERR,
                'warning' => PEAR_LOG_WARNING,
                'notice' => PEAR_LOG_NOTICE,
                'info' => PEAR_LOG_INFO,
                'debug' => PEAR_LOG_DEBUG);
            if (isset($levels[$err['level']])) {
                $level = $levels[$err['level']];
            } else {
                $level = PEAR_LOG_INFO;
            }
            $logger->log($err['message'], $level, $err);
        } else { // support non-standard logs
            call_user_func($logger, $err);
        }
    }

    /**
     * Pop an error off of the error stack
     *
     * @return false|array
     * @since 0.4alpha it is no longer possible to specify a specific error
     * level to return - the last error pushed will be returned, instead
     */
    function pop()
    {
        $err = @array_shift($this->_errors);
        if (!is_null($err)) {
            @array_pop($this->_errorsByLevel[$err['level']]);
            if (!count($this->_errorsByLevel[$err['level']])) {
                unset($this->_errorsByLevel[$err['level']]);
            }
        }
        return $err;
    }

    /**
     * Pop an error off of the error stack, static method
     *
     * @param string package name
     * @return boolean
     * @since PEAR1.5.0a1
     */
    function staticPop($package)
    {
        if ($package) {
            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
                return false;
            }
            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
        }
    }

    /**
     * Determine whether there are any errors on the stack
     * @param string|array Level name.  Use to determine if any errors
     * of level (string), or levels (array) have been pushed
     * @return boolean
     */
    function hasErrors($level = false)
    {
        if ($level) {
            return isset($this->_errorsByLevel[$level]);
        }
        return count($this->_errors);
    }

    /**
     * Retrieve all errors since last purge
     *
     * @param boolean set in order to empty the error stack
     * @param string level name, to return only errors of a particular severity
     * @return array
     */
    function getErrors($purge = false, $level = false)
    {
        if (!$purge) {
            if ($level) {
                if (!isset($this->_errorsByLevel[$level])) {
                    return array();
                } else {
                    return $this->_errorsByLevel[$level];
                }
            } else {
                return $this->_errors;
            }
        }
        if ($level) {
            $ret = $this->_errorsByLevel[$level];
            foreach ($this->_errorsByLevel[$level] as $i => $unused) {
                // entries are references to the $_errors array
                $this->_errorsByLevel[$level][$i] = false;
            }
            // array_filter removes all entries === false
            $this->_errors = array_filter($this->_errors);
            unset($this->_errorsByLevel[$level]);
            return $ret;
        }
        $ret = $this->_errors;
        $this->_errors = array();
        $this->_errorsByLevel = array();
        return $ret;
    }

    /**
     * Determine whether there are any errors on a single error stack, or on any error stack
     *
     * The optional parameter can be used to test the existence of any errors without the need of
     * singleton instantiation
     * @param string|false Package name to check for errors
     * @param string Level name to check for a particular severity
     * @return boolean
     * @static
     */
    function staticHasErrors($package = false, $level = false)
    {
        if ($package) {
            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
                return false;
            }
            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
        }
        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
            if ($obj->hasErrors($level)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get a list of all errors since last purge, organized by package
     * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
     * @param boolean $purge Set to purge the error stack of existing errors
     * @param string  $level Set to a level name in order to retrieve only errors of a particular level
     * @param boolean $merge Set to return a flat array, not organized by package
     * @param array   $sortfunc Function used to sort a merged array - default
     *        sorts by time, and should be good for most cases
     * @static
     * @return array
     */
    function staticGetErrors($purge = false, $level = false, $merge = false,
                             $sortfunc = array('PEAR_ErrorStack', '_sortErrors'))
    {
        $ret = array();
        if (!is_callable($sortfunc)) {
            $sortfunc = array('PEAR_ErrorStack', '_sortErrors');
        }
        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
            $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
            if ($test) {
                if ($merge) {
                    $ret = array_merge($ret, $test);
                } else {
                    $ret[$package] = $test;
                }
            }
        }
        if ($merge) {
            usort($ret, $sortfunc);
        }
        return $ret;
    }

    /**
     * Error sorting function, sorts by time
     * @access private
     */
    function _sortErrors($a, $b)
    {
        if ($a['time'] == $b['time']) {
            return 0;
        }
        if ($a['time'] < $b['time']) {
            return 1;
        }
        return -1;
    }

    /**
     * Standard file/line number/function/class context callback
     *
     * This function uses a backtrace generated from {@link debug_backtrace()}
     * and so will not work at all in PHP < 4.3.0.  The frame should
     * reference the frame that contains the source of the error.
     * @return array|false either array('file' => file, 'line' => line,
     *         'function' => function name, 'class' => class name) or
     *         if this doesn't work, then false
     * @param unused
     * @param integer backtrace frame.
     * @param array Results of debug_backtrace()
     * @static
     */
    function getFileLine($code, $params, $backtrace = null)
    {
        if ($backtrace === null) {
            return false;
        }
        $frame = 0;
        $functionframe = 1;
        if (!isset($backtrace[1])) {
            $functionframe = 0;
        } else {
            while (isset($backtrace[$functionframe]['function']) &&
                  $backtrace[$functionframe]['function'] == 'eval' &&
                  isset($backtrace[$functionframe + 1])) {
                $functionframe++;
            }
        }
        if (isset($backtrace[$frame])) {
            if (!isset($backtrace[$frame]['file'])) {
                $frame++;
            }
            $funcbacktrace = $backtrace[$functionframe];
            $filebacktrace = $backtrace[$frame];
            $ret = array('file' => $filebacktrace['file'],
                         'line' => $filebacktrace['line']);
            // rearrange for eval'd code or create function errors
            if (strpos($filebacktrace['file'], '(') &&
            	  preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
                  $matches)) {
                $ret['file'] = $matches[1];
                $ret['line'] = $matches[2] + 0;
            }
            if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
                if ($funcbacktrace['function'] != 'eval') {
                    if ($funcbacktrace['function'] == '__lambda_func') {
                        $ret['function'] = 'create_function() code';
                    } else {
                        $ret['function'] = $funcbacktrace['function'];
                    }
                }
            }
            if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
                $ret['class'] = $funcbacktrace['class'];
            }
            return $ret;
        }
        return false;
    }

    /**
     * Standard error message generation callback
     *
     * This method may also be called by a custom error message generator
     * to fill in template values from the params array, simply
     * set the third parameter to the error message template string to use
     *
     * The special variable %__msg% is reserved: use it only to specify
     * where a message passed in by the user should be placed in the template,
     * like so:
     *
     * Error message: %msg% - internal error
     *
     * If the message passed like so:
     *
     *
     * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
     *
     *
     * The returned error message will be "Error message: server error 500 -
     * internal error"
     * @param PEAR_ErrorStack
     * @param array
     * @param string|false Pre-generated error message template
     * @static
     * @return string
     */
    function getErrorMessage(&$stack, $err, $template = false)
    {
        if ($template) {
            $mainmsg = $template;
        } else {
            $mainmsg = $stack->getErrorMessageTemplate($err['code']);
        }
        $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
        if (is_array($err['params']) && count($err['params'])) {
            foreach ($err['params'] as $name => $val) {
                if (is_array($val)) {
                    // @ is needed in case $val is a multi-dimensional array
                    $val = @implode(', ', $val);
                }
                if (is_object($val)) {
                    if (method_exists($val, '__toString')) {
                        $val = $val->__toString();
                    } else {
                        PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
                            'warning', array('obj' => get_class($val)),
                            'object %obj% passed into getErrorMessage, but has no __toString() method');
                        $val = 'Object';
                    }
                }
                $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
            }
        }
        return $mainmsg;
    }

    /**
     * Standard Error Message Template generator from code
     * @return string
     */
    function getErrorMessageTemplate($code)
    {
        if (!isset($this->_errorMsgs[$code])) {
            return '%__msg%';
        }
        return $this->_errorMsgs[$code];
    }

    /**
     * Set the Error Message Template array
     *
     * The array format must be:
     *

     * array(error code => 'message template',...)
     *

     *
     * Error message parameters passed into {@link push()} will be used as input
     * for the error message.  If the template is 'message %foo% was %bar%', and the
     * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
     * be 'message one was six'
     * @return string
     */
    function setErrorMessageTemplate($template)
    {
        $this->_errorMsgs = $template;
    }

    /**
     * emulate PEAR::raiseError()
     *
     * @return PEAR_Error
     */
    function raiseError()
    {
        require_once 'PEAR.php';
        $args = func_get_args();
        return call_user_func_array(array('PEAR', 'raiseError'), $args);
    }
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>