summaryrefslogtreecommitdiff
path: root/extlib
diff options
context:
space:
mode:
authorZach Copley <zach@status.net>2009-11-19 20:12:46 -0800
committerZach Copley <zach@status.net>2009-11-19 20:12:46 -0800
commit4b98edf75f4e255f8c61087bd1525d89653a521f (patch)
treeb2a7eb6d77429eadb1beabe2d5e6ae1c1a2831d6 /extlib
parentf92574dbcb1f2d7cd0aaf3c9362db46fa066e888 (diff)
parentc213477081afefb1720c8ae729d1965e7a1dac63 (diff)
Merge branch '0.9-release'
* 0.9-release: (874 commits) Removed call to NewDirectMessage() until IE return is fixed i.e., Don't show flag user button your own profile Fixed HXR response for flag user Using the right form class name Using common_redirect Left a form_data class of a <ul> in the user admin panel Added validation to fields in user admin panel Added a user admin panel Added mobile logos for default and identica themes Changed gif to png Changed this to action. THANKS zach! Doing content negotiation only once Add execute bit to pingqueuehandler Localisation updates for !StatusNet from !translatewiki.net Use the browser's geolocation API to set the location on the notice form Add geometa library, and include it. Add location form elements to the noticeform, and save their values on submission Use the $user object nickname, as login name doesnt have to == nickname anymore with plugins such as ldap/etc Revert "Re added NICKNAME_FMT constant to router.php." Moved most path and server settings to a new paths admin panel ... Conflicts: js/util.js locale/it_IT/LC_MESSAGES/statusnet.mo locale/mk_MK/LC_MESSAGES/statusnet.mo locale/mk_MK/LC_MESSAGES/statusnet.po locale/pt_BR/LC_MESSAGES/statusnet.mo locale/vi_VN/LC_MESSAGES/statusnet.mo plugins/InfiniteScroll/infinitescroll.js plugins/Realtime/realtimeupdate.js
Diffstat (limited to 'extlib')
-rw-r--r--extlib/Auth/OpenID.php2
-rw-r--r--extlib/Auth/OpenID/BigMath.php2
-rw-r--r--extlib/Auth/OpenID/Consumer.php3
-rw-r--r--extlib/Auth/OpenID/Message.php5
-rw-r--r--extlib/Auth/Yadis/HTTPFetcher.php2
-rw-r--r--extlib/Auth/Yadis/ParanoidHTTPFetcher.php2
-rw-r--r--extlib/Auth/Yadis/PlainHTTPFetcher.php2
-rw-r--r--extlib/Auth/Yadis/XML.php4
-rw-r--r--extlib/DB/DataObject.php2
-rw-r--r--extlib/DB/DataObject/Cast.php8
-rw-r--r--extlib/DB/DataObject/Error.php8
-rw-r--r--extlib/HTTP/Request2.php844
-rw-r--r--extlib/HTTP/Request2/Adapter.php152
-rw-r--r--extlib/HTTP/Request2/Adapter/Curl.php383
-rw-r--r--extlib/HTTP/Request2/Adapter/Mock.php171
-rw-r--r--extlib/HTTP/Request2/Adapter/Socket.php971
-rw-r--r--extlib/HTTP/Request2/Exception.php62
-rw-r--r--extlib/HTTP/Request2/MultipartBody.php274
-rw-r--r--extlib/HTTP/Request2/Observer/Log.php215
-rw-r--r--extlib/HTTP/Request2/Response.php549
-rw-r--r--extlib/Net/LDAP2.php1791
-rw-r--r--extlib/Net/LDAP2/Entry.php1055
-rw-r--r--extlib/Net/LDAP2/Filter.php514
-rw-r--r--extlib/Net/LDAP2/LDIF.php922
-rw-r--r--extlib/Net/LDAP2/RootDSE.php240
-rw-r--r--extlib/Net/LDAP2/Schema.php516
-rw-r--r--extlib/Net/LDAP2/SchemaCache.interface.php59
-rw-r--r--extlib/Net/LDAP2/Search.php614
-rw-r--r--extlib/Net/LDAP2/SimpleFileSchemaCache.php97
-rw-r--r--extlib/Net/LDAP2/Util.php572
-rw-r--r--extlib/Net/URL2.php471
-rw-r--r--extlib/PEAR.php2
-rw-r--r--extlib/Stomp.php135
-rw-r--r--extlib/Stomp/Exception.php8
-rw-r--r--extlib/Stomp/Frame.php86
-rw-r--r--extlib/Stomp/Message.php4
-rw-r--r--extlib/Stomp/Message/Map.php12
-rw-r--r--extlib/facebook/facebook.php598
-rw-r--r--extlib/facebook/facebook_desktop.php104
-rwxr-xr-xextlib/facebook/facebookapi_php5_restlib.php3618
-rw-r--r--extlib/facebook/jsonwrapper/JSON/JSON.php806
-rw-r--r--extlib/facebook/jsonwrapper/JSON/LICENSE21
-rw-r--r--extlib/facebook/jsonwrapper/jsonwrapper.php6
-rw-r--r--extlib/facebook/jsonwrapper/jsonwrapper_inner.php23
-rwxr-xr-xextlib/libomb/base_url_xrds_mapper.php51
-rw-r--r--extlib/libomb/constants.php58
-rwxr-xr-xextlib/libomb/datastore.php200
-rw-r--r--extlib/libomb/helper.php99
-rwxr-xr-xextlib/libomb/invalidparameterexception.php32
-rwxr-xr-xextlib/libomb/invalidyadisexception.php31
-rwxr-xr-xextlib/libomb/notice.php272
-rwxr-xr-xextlib/libomb/omb_yadis_xrds.php196
-rwxr-xr-xextlib/libomb/plain_xrds_writer.php124
-rwxr-xr-xextlib/libomb/profile.php317
-rwxr-xr-xextlib/libomb/remoteserviceexception.php42
-rwxr-xr-xextlib/libomb/service_consumer.php430
-rwxr-xr-xextlib/libomb/service_provider.php425
-rwxr-xr-xextlib/libomb/unsupportedserviceexception.php31
-rwxr-xr-xextlib/libomb/xrds_mapper.php33
-rwxr-xr-xextlib/libomb/xrds_writer.php33
-rw-r--r--extlib/php-gettext/gettext.inc2
61 files changed, 12818 insertions, 5493 deletions
diff --git a/extlib/Auth/OpenID.php b/extlib/Auth/OpenID.php
index 6556b5b01..db6164256 100644
--- a/extlib/Auth/OpenID.php
+++ b/extlib/Auth/OpenID.php
@@ -20,7 +20,7 @@
/**
* The library version string
*/
-define('Auth_OpenID_VERSION', '2.1.2');
+define('Auth_OpenID_VERSION', '2.1.3');
/**
* Require the fetcher code.
diff --git a/extlib/Auth/OpenID/BigMath.php b/extlib/Auth/OpenID/BigMath.php
index b5fc627a0..45104947d 100644
--- a/extlib/Auth/OpenID/BigMath.php
+++ b/extlib/Auth/OpenID/BigMath.php
@@ -376,7 +376,7 @@ function Auth_OpenID_detectMathLibrary($exts)
// Try to load dynamic modules.
if (!$loaded) {
foreach ($extension['modules'] as $module) {
- if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($module . "." . PHP_SHLIB_SUFFIX)) {
+ if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
$loaded = true;
break;
}
diff --git a/extlib/Auth/OpenID/Consumer.php b/extlib/Auth/OpenID/Consumer.php
index a72684c6b..500890b65 100644
--- a/extlib/Auth/OpenID/Consumer.php
+++ b/extlib/Auth/OpenID/Consumer.php
@@ -1295,7 +1295,8 @@ class Auth_OpenID_GenericConsumer {
Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
array('response_nonce',
'claimed_id',
- 'assoc_handle')),
+ 'assoc_handle',
+ 'op_endpoint')),
Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
array('nonce'))
);
diff --git a/extlib/Auth/OpenID/Message.php b/extlib/Auth/OpenID/Message.php
index fd23e67a3..5ab115a86 100644
--- a/extlib/Auth/OpenID/Message.php
+++ b/extlib/Auth/OpenID/Message.php
@@ -887,6 +887,11 @@ class Auth_OpenID_Message {
function getAliasedArg($aliased_key, $default = null)
{
+ if ($aliased_key == 'ns') {
+ // Return the namespace URI for the OpenID namespace
+ return $this->getOpenIDNamespace();
+ }
+
$parts = explode('.', $aliased_key, 2);
if (count($parts) != 2) {
diff --git a/extlib/Auth/Yadis/HTTPFetcher.php b/extlib/Auth/Yadis/HTTPFetcher.php
index a1825403d..963b9a49a 100644
--- a/extlib/Auth/Yadis/HTTPFetcher.php
+++ b/extlib/Auth/Yadis/HTTPFetcher.php
@@ -138,7 +138,7 @@ class Auth_Yadis_HTTPFetcher {
* pass the URLHasAllowedScheme check or if the server's response
* is malformed.
*/
- function get($url, $headers)
+ function get($url, $headers = null)
{
trigger_error("not implemented", E_USER_ERROR);
}
diff --git a/extlib/Auth/Yadis/ParanoidHTTPFetcher.php b/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
index 8975d7f89..6a418260e 100644
--- a/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
+++ b/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
@@ -127,8 +127,6 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
curl_setopt($c, CURLOPT_TIMEOUT, $off);
curl_setopt($c, CURLOPT_URL, $url);
- curl_setopt($c, CURLOPT_RANGE,
- "0-".(1024 * Auth_OpenID_FETCHER_MAX_RESPONSE_KB));
curl_exec($c);
diff --git a/extlib/Auth/Yadis/PlainHTTPFetcher.php b/extlib/Auth/Yadis/PlainHTTPFetcher.php
index 8882e3cef..3e0ca2bb0 100644
--- a/extlib/Auth/Yadis/PlainHTTPFetcher.php
+++ b/extlib/Auth/Yadis/PlainHTTPFetcher.php
@@ -83,8 +83,6 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
"User-Agent: $user_agent",
"Host: ".$parts['host'].
($specify_port ? ":".$parts['port'] : ""),
- "Range: 0-".
- (1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB),
"Port: ".$parts['port']);
$errno = 0;
diff --git a/extlib/Auth/Yadis/XML.php b/extlib/Auth/Yadis/XML.php
index 7232d6cbd..81b2ce221 100644
--- a/extlib/Auth/Yadis/XML.php
+++ b/extlib/Auth/Yadis/XML.php
@@ -91,7 +91,7 @@ class Auth_Yadis_XMLParser {
* @return array $node_list An array of matching opaque node
* objects to be used with other methods of this parser class.
*/
- function evalXPath($xpath, $node = null)
+ function &evalXPath($xpath, $node = null)
{
// Not implemented.
}
@@ -349,7 +349,7 @@ function &Auth_Yadis_getXMLParser()
foreach ($extensions as $name => $params) {
if (!extension_loaded($name)) {
foreach ($params['libname'] as $libname) {
- if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($libname)) {
+ if (@dl($libname)) {
$classname = $params['classname'];
}
}
diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php
index 8e226b8fa..60ff1441b 100644
--- a/extlib/DB/DataObject.php
+++ b/extlib/DB/DataObject.php
@@ -235,7 +235,7 @@ class DB_DataObject extends DB_DataObject_Overload
* @access private
* @var string
*/
- var $_DB_DataObject_version = "1.8.11";
+ var $_DB_DataObject_version = "1.8.12";
/**
* The Database table (used by table extends)
diff --git a/extlib/DB/DataObject/Cast.php b/extlib/DB/DataObject/Cast.php
index 095d2a4d2..2049beb58 100644
--- a/extlib/DB/DataObject/Cast.php
+++ b/extlib/DB/DataObject/Cast.php
@@ -6,9 +6,9 @@
*
* PHP versions 4 and 5
*
- * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
@@ -16,8 +16,8 @@
* @package DB_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2008 The PHP Group
- * @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version CVS: $Id: Cast.php 264148 2008-08-04 03:44:59Z alan_k $
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Cast.php 287158 2009-08-12 13:58:31Z alan_k $
* @link http://pear.php.net/package/DB_DataObject
*/
diff --git a/extlib/DB/DataObject/Error.php b/extlib/DB/DataObject/Error.php
index 382115453..cdb863d83 100644
--- a/extlib/DB/DataObject/Error.php
+++ b/extlib/DB/DataObject/Error.php
@@ -7,9 +7,9 @@
*
* PHP versions 4 and 5
*
- * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
@@ -17,8 +17,8 @@
* @package DB_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group
- * @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version CVS: $Id: Error.php 277015 2009-03-12 05:51:03Z alan_k $
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Error.php 287158 2009-08-12 13:58:31Z alan_k $
* @link http://pear.php.net/package/DB_DataObject
*/
diff --git a/extlib/HTTP/Request2.php b/extlib/HTTP/Request2.php
new file mode 100644
index 000000000..e06bb86bc
--- /dev/null
+++ b/extlib/HTTP/Request2.php
@@ -0,0 +1,844 @@
+<?php
+/**
+ * Class representing a HTTP request
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Request2.php 278226 2009-04-03 21:32:48Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * A class representing an URL as per RFC 3986.
+ */
+require_once 'Net/URL2.php';
+
+/**
+ * Exception class for HTTP_Request2 package
+ */
+require_once 'HTTP/Request2/Exception.php';
+
+/**
+ * Class representing a HTTP request
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ * @link http://tools.ietf.org/html/rfc2616#section-5
+ */
+class HTTP_Request2 implements SplSubject
+{
+ /**#@+
+ * Constants for HTTP request methods
+ *
+ * @link http://tools.ietf.org/html/rfc2616#section-5.1.1
+ */
+ const METHOD_OPTIONS = 'OPTIONS';
+ const METHOD_GET = 'GET';
+ const METHOD_HEAD = 'HEAD';
+ const METHOD_POST = 'POST';
+ const METHOD_PUT = 'PUT';
+ const METHOD_DELETE = 'DELETE';
+ const METHOD_TRACE = 'TRACE';
+ const METHOD_CONNECT = 'CONNECT';
+ /**#@-*/
+
+ /**#@+
+ * Constants for HTTP authentication schemes
+ *
+ * @link http://tools.ietf.org/html/rfc2617
+ */
+ const AUTH_BASIC = 'basic';
+ const AUTH_DIGEST = 'digest';
+ /**#@-*/
+
+ /**
+ * Regular expression used to check for invalid symbols in RFC 2616 tokens
+ * @link http://pear.php.net/bugs/bug.php?id=15630
+ */
+ const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!';
+
+ /**
+ * Regular expression used to check for invalid symbols in cookie strings
+ * @link http://pear.php.net/bugs/bug.php?id=15630
+ * @link http://cgi.netscape.com/newsref/std/cookie_spec.html
+ */
+ const REGEXP_INVALID_COOKIE = '/[\s,;]/';
+
+ /**
+ * Fileinfo magic database resource
+ * @var resource
+ * @see detectMimeType()
+ */
+ private static $_fileinfoDb;
+
+ /**
+ * Observers attached to the request (instances of SplObserver)
+ * @var array
+ */
+ protected $observers = array();
+
+ /**
+ * Request URL
+ * @var Net_URL2
+ */
+ protected $url;
+
+ /**
+ * Request method
+ * @var string
+ */
+ protected $method = self::METHOD_GET;
+
+ /**
+ * Authentication data
+ * @var array
+ * @see getAuth()
+ */
+ protected $auth;
+
+ /**
+ * Request headers
+ * @var array
+ */
+ protected $headers = array();
+
+ /**
+ * Configuration parameters
+ * @var array
+ * @see setConfig()
+ */
+ protected $config = array(
+ 'adapter' => 'HTTP_Request2_Adapter_Socket',
+ 'connect_timeout' => 10,
+ 'timeout' => 0,
+ 'use_brackets' => true,
+ 'protocol_version' => '1.1',
+ 'buffer_size' => 16384,
+ 'store_body' => true,
+
+ 'proxy_host' => '',
+ 'proxy_port' => '',
+ 'proxy_user' => '',
+ 'proxy_password' => '',
+ 'proxy_auth_scheme' => self::AUTH_BASIC,
+
+ 'ssl_verify_peer' => true,
+ 'ssl_verify_host' => true,
+ 'ssl_cafile' => null,
+ 'ssl_capath' => null,
+ 'ssl_local_cert' => null,
+ 'ssl_passphrase' => null,
+
+ 'digest_compat_ie' => false
+ );
+
+ /**
+ * Last event in request / response handling, intended for observers
+ * @var array
+ * @see getLastEvent()
+ */
+ protected $lastEvent = array(
+ 'name' => 'start',
+ 'data' => null
+ );
+
+ /**
+ * Request body
+ * @var string|resource
+ * @see setBody()
+ */
+ protected $body = '';
+
+ /**
+ * Array of POST parameters
+ * @var array
+ */
+ protected $postParams = array();
+
+ /**
+ * Array of file uploads (for multipart/form-data POST requests)
+ * @var array
+ */
+ protected $uploads = array();
+
+ /**
+ * Adapter used to perform actual HTTP request
+ * @var HTTP_Request2_Adapter
+ */
+ protected $adapter;
+
+
+ /**
+ * Constructor. Can set request URL, method and configuration array.
+ *
+ * Also sets a default value for User-Agent header.
+ *
+ * @param string|Net_Url2 Request URL
+ * @param string Request method
+ * @param array Configuration for this Request instance
+ */
+ public function __construct($url = null, $method = self::METHOD_GET, array $config = array())
+ {
+ if (!empty($url)) {
+ $this->setUrl($url);
+ }
+ if (!empty($method)) {
+ $this->setMethod($method);
+ }
+ $this->setConfig($config);
+ $this->setHeader('user-agent', 'HTTP_Request2/0.4.1 ' .
+ '(http://pear.php.net/package/http_request2) ' .
+ 'PHP/' . phpversion());
+ }
+
+ /**
+ * Sets the URL for this request
+ *
+ * If the URL has userinfo part (username & password) these will be removed
+ * and converted to auth data. If the URL does not have a path component,
+ * that will be set to '/'.
+ *
+ * @param string|Net_URL2 Request URL
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception
+ */
+ public function setUrl($url)
+ {
+ if (is_string($url)) {
+ $url = new Net_URL2($url);
+ }
+ if (!$url instanceof Net_URL2) {
+ throw new HTTP_Request2_Exception('Parameter is not a valid HTTP URL');
+ }
+ // URL contains username / password?
+ if ($url->getUserinfo()) {
+ $username = $url->getUser();
+ $password = $url->getPassword();
+ $this->setAuth(rawurldecode($username), $password? rawurldecode($password): '');
+ $url->setUserinfo('');
+ }
+ if ('' == $url->getPath()) {
+ $url->setPath('/');
+ }
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * Returns the request URL
+ *
+ * @return Net_URL2
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Sets the request method
+ *
+ * @param string
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception if the method name is invalid
+ */
+ public function setMethod($method)
+ {
+ // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1
+ if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {
+ throw new HTTP_Request2_Exception("Invalid request method '{$method}'");
+ }
+ $this->method = $method;
+
+ return $this;
+ }
+
+ /**
+ * Returns the request method
+ *
+ * @return string
+ */
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ /**
+ * Sets the configuration parameter(s)
+ *
+ * The following parameters are available:
+ * <ul>
+ * <li> 'adapter' - adapter to use (string)</li>
+ * <li> 'connect_timeout' - Connection timeout in seconds (integer)</li>
+ * <li> 'timeout' - Total number of seconds a request can take.
+ * Use 0 for no limit, should be greater than
+ * 'connect_timeout' if set (integer)</li>
+ * <li> 'use_brackets' - Whether to append [] to array variable names (bool)</li>
+ * <li> 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)</li>
+ * <li> 'buffer_size' - Buffer size to use for reading and writing (int)</li>
+ * <li> 'store_body' - Whether to store response body in response object.
+ * Set to false if receiving a huge response and
+ * using an Observer to save it (boolean)</li>
+ * <li> 'proxy_host' - Proxy server host (string)</li>
+ * <li> 'proxy_port' - Proxy server port (integer)</li>
+ * <li> 'proxy_user' - Proxy auth username (string)</li>
+ * <li> 'proxy_password' - Proxy auth password (string)</li>
+ * <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li>
+ * <li> 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)</li>
+ * <li> 'ssl_verify_host' - Whether to check that Common Name in SSL
+ * certificate matches host name (bool)</li>
+ * <li> 'ssl_cafile' - Cerificate Authority file to verify the peer
+ * with (use with 'ssl_verify_peer') (string)</li>
+ * <li> 'ssl_capath' - Directory holding multiple Certificate
+ * Authority files (string)</li>
+ * <li> 'ssl_local_cert' - Name of a file containing local cerificate (string)</li>
+ * <li> 'ssl_passphrase' - Passphrase with which local certificate
+ * was encoded (string)</li>
+ * <li> 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6
+ * in using URL without query string in digest
+ * authentication (boolean)</li>
+ * </ul>
+ *
+ * @param string|array configuration parameter name or array
+ * ('parameter name' => 'parameter value')
+ * @param mixed parameter value if $nameOrConfig is not an array
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception If the parameter is unknown
+ */
+ public function setConfig($nameOrConfig, $value = null)
+ {
+ if (is_array($nameOrConfig)) {
+ foreach ($nameOrConfig as $name => $value) {
+ $this->setConfig($name, $value);
+ }
+
+ } else {
+ if (!array_key_exists($nameOrConfig, $this->config)) {
+ throw new HTTP_Request2_Exception(
+ "Unknown configuration parameter '{$nameOrConfig}'"
+ );
+ }
+ $this->config[$nameOrConfig] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the value(s) of the configuration parameter(s)
+ *
+ * @param string parameter name
+ * @return mixed value of $name parameter, array of all configuration
+ * parameters if $name is not given
+ * @throws HTTP_Request2_Exception If the parameter is unknown
+ */
+ public function getConfig($name = null)
+ {
+ if (null === $name) {
+ return $this->config;
+ } elseif (!array_key_exists($name, $this->config)) {
+ throw new HTTP_Request2_Exception(
+ "Unknown configuration parameter '{$name}'"
+ );
+ }
+ return $this->config[$name];
+ }
+
+ /**
+ * Sets the autentification data
+ *
+ * @param string user name
+ * @param string password
+ * @param string authentication scheme
+ * @return HTTP_Request2
+ */
+ public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)
+ {
+ if (empty($user)) {
+ $this->auth = null;
+ } else {
+ $this->auth = array(
+ 'user' => (string)$user,
+ 'password' => (string)$password,
+ 'scheme' => $scheme
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the authentication data
+ *
+ * The array has the keys 'user', 'password' and 'scheme', where 'scheme'
+ * is one of the HTTP_Request2::AUTH_* constants.
+ *
+ * @return array
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+
+ /**
+ * Sets request header(s)
+ *
+ * The first parameter may be either a full header string 'header: value' or
+ * header name. In the former case $value parameter is ignored, in the latter
+ * the header's value will either be set to $value or the header will be
+ * removed if $value is null. The first parameter can also be an array of
+ * headers, in that case method will be called recursively.
+ *
+ * Note that headers are treated case insensitively as per RFC 2616.
+ *
+ * <code>
+ * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'
+ * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'
+ * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux'
+ * $req->setHeader('FOO'); // removes 'Foo' header from request
+ * </code>
+ *
+ * @param string|array header name, header string ('Header: value')
+ * or an array of headers
+ * @param string|null header value, header will be removed if null
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception
+ */
+ public function setHeader($name, $value = null)
+ {
+ if (is_array($name)) {
+ foreach ($name as $k => $v) {
+ if (is_string($k)) {
+ $this->setHeader($k, $v);
+ } else {
+ $this->setHeader($v);
+ }
+ }
+ } else {
+ if (null === $value && strpos($name, ':')) {
+ list($name, $value) = array_map('trim', explode(':', $name, 2));
+ }
+ // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2
+ if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {
+ throw new HTTP_Request2_Exception("Invalid header name '{$name}'");
+ }
+ // Header names are case insensitive anyway
+ $name = strtolower($name);
+ if (null === $value) {
+ unset($this->headers[$name]);
+ } else {
+ $this->headers[$name] = $value;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the request headers
+ *
+ * The array is of the form ('header name' => 'header value'), header names
+ * are lowercased
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Appends a cookie to "Cookie:" header
+ *
+ * @param string cookie name
+ * @param string cookie value
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception
+ */
+ public function addCookie($name, $value)
+ {
+ $cookie = $name . '=' . $value;
+ if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {
+ throw new HTTP_Request2_Exception("Invalid cookie: '{$cookie}'");
+ }
+ $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';
+ $this->setHeader('cookie', $cookies . $cookie);
+
+ return $this;
+ }
+
+ /**
+ * Sets the request body
+ *
+ * @param string Either a string with the body or filename containing body
+ * @param bool Whether first parameter is a filename
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception
+ */
+ public function setBody($body, $isFilename = false)
+ {
+ if (!$isFilename) {
+ $this->body = (string)$body;
+ } else {
+ if (!($fp = @fopen($body, 'rb'))) {
+ throw new HTTP_Request2_Exception("Cannot open file {$body}");
+ }
+ $this->body = $fp;
+ if (empty($this->headers['content-type'])) {
+ $this->setHeader('content-type', self::detectMimeType($body));
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the request body
+ *
+ * @return string|resource|HTTP_Request2_MultipartBody
+ */
+ public function getBody()
+ {
+ if (self::METHOD_POST == $this->method &&
+ (!empty($this->postParams) || !empty($this->uploads))
+ ) {
+ if ('application/x-www-form-urlencoded' == $this->headers['content-type']) {
+ $body = http_build_query($this->postParams, '', '&');
+ if (!$this->getConfig('use_brackets')) {
+ $body = preg_replace('/%5B\d+%5D=/', '=', $body);
+ }
+ // support RFC 3986 by not encoding '~' symbol (request #15368)
+ return str_replace('%7E', '~', $body);
+
+ } elseif ('multipart/form-data' == $this->headers['content-type']) {
+ require_once 'HTTP/Request2/MultipartBody.php';
+ return new HTTP_Request2_MultipartBody(
+ $this->postParams, $this->uploads, $this->getConfig('use_brackets')
+ );
+ }
+ }
+ return $this->body;
+ }
+
+ /**
+ * Adds a file to form-based file upload
+ *
+ * Used to emulate file upload via a HTML form. The method also sets
+ * Content-Type of HTTP request to 'multipart/form-data'.
+ *
+ * If you just want to send the contents of a file as the body of HTTP
+ * request you should use setBody() method.
+ *
+ * @param string name of file-upload field
+ * @param mixed full name of local file
+ * @param string filename to send in the request
+ * @param string content-type of file being uploaded
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception
+ */
+ public function addUpload($fieldName, $filename, $sendFilename = null,
+ $contentType = null)
+ {
+ if (!is_array($filename)) {
+ if (!($fp = @fopen($filename, 'rb'))) {
+ throw new HTTP_Request2_Exception("Cannot open file {$filename}");
+ }
+ $this->uploads[$fieldName] = array(
+ 'fp' => $fp,
+ 'filename' => empty($sendFilename)? basename($filename): $sendFilename,
+ 'size' => filesize($filename),
+ 'type' => empty($contentType)? self::detectMimeType($filename): $contentType
+ );
+ } else {
+ $fps = $names = $sizes = $types = array();
+ foreach ($filename as $f) {
+ if (!is_array($f)) {
+ $f = array($f);
+ }
+ if (!($fp = @fopen($f[0], 'rb'))) {
+ throw new HTTP_Request2_Exception("Cannot open file {$f[0]}");
+ }
+ $fps[] = $fp;
+ $names[] = empty($f[1])? basename($f[0]): $f[1];
+ $sizes[] = filesize($f[0]);
+ $types[] = empty($f[2])? self::detectMimeType($f[0]): $f[2];
+ }
+ $this->uploads[$fieldName] = array(
+ 'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types
+ );
+ }
+ if (empty($this->headers['content-type']) ||
+ 'application/x-www-form-urlencoded' == $this->headers['content-type']
+ ) {
+ $this->setHeader('content-type', 'multipart/form-data');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds POST parameter(s) to the request.
+ *
+ * @param string|array parameter name or array ('name' => 'value')
+ * @param mixed parameter value (can be an array)
+ * @return HTTP_Request2
+ */
+ public function addPostParameter($name, $value = null)
+ {
+ if (!is_array($name)) {
+ $this->postParams[$name] = $value;
+ } else {
+ foreach ($name as $k => $v) {
+ $this->addPostParameter($k, $v);
+ }
+ }
+ if (empty($this->headers['content-type'])) {
+ $this->setHeader('content-type', 'application/x-www-form-urlencoded');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Attaches a new observer
+ *
+ * @param SplObserver
+ */
+ public function attach(SplObserver $observer)
+ {
+ foreach ($this->observers as $attached) {
+ if ($attached === $observer) {
+ return;
+ }
+ }
+ $this->observers[] = $observer;
+ }
+
+ /**
+ * Detaches an existing observer
+ *
+ * @param SplObserver
+ */
+ public function detach(SplObserver $observer)
+ {
+ foreach ($this->observers as $key => $attached) {
+ if ($attached === $observer) {
+ unset($this->observers[$key]);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Notifies all observers
+ */
+ public function notify()
+ {
+ foreach ($this->observers as $observer) {
+ $observer->update($this);
+ }
+ }
+
+ /**
+ * Sets the last event
+ *
+ * Adapters should use this method to set the current state of the request
+ * and notify the observers.
+ *
+ * @param string event name
+ * @param mixed event data
+ */
+ public function setLastEvent($name, $data = null)
+ {
+ $this->lastEvent = array(
+ 'name' => $name,
+ 'data' => $data
+ );
+ $this->notify();
+ }
+
+ /**
+ * Returns the last event
+ *
+ * Observers should use this method to access the last change in request.
+ * The following event names are possible:
+ * <ul>
+ * <li>'connect' - after connection to remote server,
+ * data is the destination (string)</li>
+ * <li>'disconnect' - after disconnection from server</li>
+ * <li>'sentHeaders' - after sending the request headers,
+ * data is the headers sent (string)</li>
+ * <li>'sentBodyPart' - after sending a part of the request body,
+ * data is the length of that part (int)</li>
+ * <li>'receivedHeaders' - after receiving the response headers,
+ * data is HTTP_Request2_Response object</li>
+ * <li>'receivedBodyPart' - after receiving a part of the response
+ * body, data is that part (string)</li>
+ * <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still
+ * encoded by Content-Encoding</li>
+ * <li>'receivedBody' - after receiving the complete response
+ * body, data is HTTP_Request2_Response object</li>
+ * </ul>
+ * Different adapters may not send all the event types. Mock adapter does
+ * not send any events to the observers.
+ *
+ * @return array The array has two keys: 'name' and 'data'
+ */
+ public function getLastEvent()
+ {
+ return $this->lastEvent;
+ }
+
+ /**
+ * Sets the adapter used to actually perform the request
+ *
+ * You can pass either an instance of a class implementing HTTP_Request2_Adapter
+ * or a class name. The method will only try to include a file if the class
+ * name starts with HTTP_Request2_Adapter_, it will also try to prepend this
+ * prefix to the class name if it doesn't contain any underscores, so that
+ * <code>
+ * $request->setAdapter('curl');
+ * </code>
+ * will work.
+ *
+ * @param string|HTTP_Request2_Adapter
+ * @return HTTP_Request2
+ * @throws HTTP_Request2_Exception
+ */
+ public function setAdapter($adapter)
+ {
+ if (is_string($adapter)) {
+ if (!class_exists($adapter, false)) {
+ if (false === strpos($adapter, '_')) {
+ $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter);
+ }
+ if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) {
+ include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';
+ }
+ if (!class_exists($adapter, false)) {
+ throw new HTTP_Request2_Exception("Class {$adapter} not found");
+ }
+ }
+ $adapter = new $adapter;
+ }
+ if (!$adapter instanceof HTTP_Request2_Adapter) {
+ throw new HTTP_Request2_Exception('Parameter is not a HTTP request adapter');
+ }
+ $this->adapter = $adapter;
+
+ return $this;
+ }
+
+ /**
+ * Sends the request and returns the response
+ *
+ * @throws HTTP_Request2_Exception
+ * @return HTTP_Request2_Response
+ */
+ public function send()
+ {
+ // Sanity check for URL
+ if (!$this->url instanceof Net_URL2) {
+ throw new HTTP_Request2_Exception('No URL given');
+ } elseif (!$this->url->isAbsolute()) {
+ throw new HTTP_Request2_Exception('Absolute URL required');
+ } elseif (!in_array(strtolower($this->url->getScheme()), array('https', 'http'))) {
+ throw new HTTP_Request2_Exception('Not a HTTP URL');
+ }
+ if (empty($this->adapter)) {
+ $this->setAdapter($this->getConfig('adapter'));
+ }
+ // magic_quotes_runtime may break file uploads and chunked response
+ // processing; see bug #4543
+ if ($magicQuotes = ini_get('magic_quotes_runtime')) {
+ ini_set('magic_quotes_runtime', false);
+ }
+ // force using single byte encoding if mbstring extension overloads
+ // strlen() and substr(); see bug #1781, bug #10605
+ if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
+ $oldEncoding = mb_internal_encoding();
+ mb_internal_encoding('iso-8859-1');
+ }
+
+ try {
+ $response = $this->adapter->sendRequest($this);
+ } catch (Exception $e) {
+ }
+ // cleanup in either case (poor man's "finally" clause)
+ if ($magicQuotes) {
+ ini_set('magic_quotes_runtime', true);
+ }
+ if (!empty($oldEncoding)) {
+ mb_internal_encoding($oldEncoding);
+ }
+ // rethrow the exception
+ if (!empty($e)) {
+ throw $e;
+ }
+ return $response;
+ }
+
+ /**
+ * Tries to detect MIME type of a file
+ *
+ * The method will try to use fileinfo extension if it is available,
+ * deprecated mime_content_type() function in the other case. If neither
+ * works, default 'application/octet-stream' MIME type is returned
+ *
+ * @param string filename
+ * @return string file MIME type
+ */
+ protected static function detectMimeType($filename)
+ {
+ // finfo extension from PECL available
+ if (function_exists('finfo_open')) {
+ if (!isset(self::$_fileinfoDb)) {
+ self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);
+ }
+ if (self::$_fileinfoDb) {
+ $info = finfo_file(self::$_fileinfoDb, $filename);
+ }
+ }
+ // (deprecated) mime_content_type function available
+ if (empty($info) && function_exists('mime_content_type')) {
+ return mime_content_type($filename);
+ }
+ return empty($info)? 'application/octet-stream': $info;
+ }
+}
+?> \ No newline at end of file
diff --git a/extlib/HTTP/Request2/Adapter.php b/extlib/HTTP/Request2/Adapter.php
new file mode 100644
index 000000000..39b092b34
--- /dev/null
+++ b/extlib/HTTP/Request2/Adapter.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Base class for HTTP_Request2 adapters
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Adapter.php 274684 2009-01-26 23:07:27Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Class representing a HTTP response
+ */
+require_once 'HTTP/Request2/Response.php';
+
+/**
+ * Base class for HTTP_Request2 adapters
+ *
+ * HTTP_Request2 class itself only defines methods for aggregating the request
+ * data, all actual work of sending the request to the remote server and
+ * receiving its response is performed by adapters.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ */
+abstract class HTTP_Request2_Adapter
+{
+ /**
+ * A list of methods that MUST NOT have a request body, per RFC 2616
+ * @var array
+ */
+ protected static $bodyDisallowed = array('TRACE');
+
+ /**
+ * Methods having defined semantics for request body
+ *
+ * Content-Length header (indicating that the body follows, section 4.3 of
+ * RFC 2616) will be sent for these methods even if no body was added
+ *
+ * @var array
+ * @link http://pear.php.net/bugs/bug.php?id=12900
+ * @link http://pear.php.net/bugs/bug.php?id=14740
+ */
+ protected static $bodyRequired = array('POST', 'PUT');
+
+ /**
+ * Request being sent
+ * @var HTTP_Request2
+ */
+ protected $request;
+
+ /**
+ * Request body
+ * @var string|resource|HTTP_Request2_MultipartBody
+ * @see HTTP_Request2::getBody()
+ */
+ protected $requestBody;
+
+ /**
+ * Length of the request body
+ * @var integer
+ */
+ protected $contentLength;
+
+ /**
+ * Sends request to the remote server and returns its response
+ *
+ * @param HTTP_Request2
+ * @return HTTP_Request2_Response
+ * @throws HTTP_Request2_Exception
+ */
+ abstract public function sendRequest(HTTP_Request2 $request);
+
+ /**
+ * Calculates length of the request body, adds proper headers
+ *
+ * @param array associative array of request headers, this method will
+ * add proper 'Content-Length' and 'Content-Type' headers
+ * to this array (or remove them if not needed)
+ */
+ protected function calculateRequestLength(&$headers)
+ {
+ $this->requestBody = $this->request->getBody();
+
+ if (is_string($this->requestBody)) {
+ $this->contentLength = strlen($this->requestBody);
+ } elseif (is_resource($this->requestBody)) {
+ $stat = fstat($this->requestBody);
+ $this->contentLength = $stat['size'];
+ rewind($this->requestBody);
+ } else {
+ $this->contentLength = $this->requestBody->getLength();
+ $headers['content-type'] = 'multipart/form-data; boundary=' .
+ $this->requestBody->getBoundary();
+ $this->requestBody->rewind();
+ }
+
+ if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
+ 0 == $this->contentLength
+ ) {
+ unset($headers['content-type']);
+ // No body: send a Content-Length header nonetheless (request #12900),
+ // but do that only for methods that require a body (bug #14740)
+ if (in_array($this->request->getMethod(), self::$bodyRequired)) {
+ $headers['content-length'] = 0;
+ } else {
+ unset($headers['content-length']);
+ }
+ } else {
+ if (empty($headers['content-type'])) {
+ $headers['content-type'] = 'application/x-www-form-urlencoded';
+ }
+ $headers['content-length'] = $this->contentLength;
+ }
+ }
+}
+?>
diff --git a/extlib/HTTP/Request2/Adapter/Curl.php b/extlib/HTTP/Request2/Adapter/Curl.php
new file mode 100644
index 000000000..4d4de0dcc
--- /dev/null
+++ b/extlib/HTTP/Request2/Adapter/Curl.php
@@ -0,0 +1,383 @@
+<?php
+/**
+ * Adapter for HTTP_Request2 wrapping around cURL extension
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Curl.php 278226 2009-04-03 21:32:48Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for HTTP_Request2 adapters
+ */
+require_once 'HTTP/Request2/Adapter.php';
+
+/**
+ * Adapter for HTTP_Request2 wrapping around cURL extension
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ */
+class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
+{
+ /**
+ * Mapping of header names to cURL options
+ * @var array
+ */
+ protected static $headerMap = array(
+ 'accept-encoding' => CURLOPT_ENCODING,
+ 'cookie' => CURLOPT_COOKIE,
+ 'referer' => CURLOPT_REFERER,
+ 'user-agent' => CURLOPT_USERAGENT
+ );
+
+ /**
+ * Mapping of SSL context options to cURL options
+ * @var array
+ */
+ protected static $sslContextMap = array(
+ 'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,
+ 'ssl_cafile' => CURLOPT_CAINFO,
+ 'ssl_capath' => CURLOPT_CAPATH,
+ 'ssl_local_cert' => CURLOPT_SSLCERT,
+ 'ssl_passphrase' => CURLOPT_SSLCERTPASSWD
+ );
+
+ /**
+ * Response being received
+ * @var HTTP_Request2_Response
+ */
+ protected $response;
+
+ /**
+ * Whether 'sentHeaders' event was sent to observers
+ * @var boolean
+ */
+ protected $eventSentHeaders = false;
+
+ /**
+ * Whether 'receivedHeaders' event was sent to observers
+ * @var boolean
+ */
+ protected $eventReceivedHeaders = false;
+
+ /**
+ * Position within request body
+ * @var integer
+ * @see callbackReadBody()
+ */
+ protected $position = 0;
+
+ /**
+ * Information about last transfer, as returned by curl_getinfo()
+ * @var array
+ */
+ protected $lastInfo;
+
+ /**
+ * Sends request to the remote server and returns its response
+ *
+ * @param HTTP_Request2
+ * @return HTTP_Request2_Response
+ * @throws HTTP_Request2_Exception
+ */
+ public function sendRequest(HTTP_Request2 $request)
+ {
+ if (!extension_loaded('curl')) {
+ throw new HTTP_Request2_Exception('cURL extension not available');
+ }
+
+ $this->request = $request;
+ $this->response = null;
+ $this->position = 0;
+ $this->eventSentHeaders = false;
+ $this->eventReceivedHeaders = false;
+
+ try {
+ if (false === curl_exec($ch = $this->createCurlHandle())) {
+ $errorMessage = 'Error sending request: #' . curl_errno($ch) .
+ ' ' . curl_error($ch);
+ }
+ } catch (Exception $e) {
+ }
+ $this->lastInfo = curl_getinfo($ch);
+ curl_close($ch);
+
+ if (!empty($e)) {
+ throw $e;
+ } elseif (!empty($errorMessage)) {
+ throw new HTTP_Request2_Exception($errorMessage);
+ }
+
+ if (0 < $this->lastInfo['size_download']) {
+ $this->request->setLastEvent('receivedBody', $this->response);
+ }
+ return $this->response;
+ }
+
+ /**
+ * Returns information about last transfer
+ *
+ * @return array associative array as returned by curl_getinfo()
+ */
+ public function getInfo()
+ {
+ return $this->lastInfo;
+ }
+
+ /**
+ * Creates a new cURL handle and populates it with data from the request
+ *
+ * @return resource a cURL handle, as created by curl_init()
+ * @throws HTTP_Request2_Exception
+ */
+ protected function createCurlHandle()
+ {
+ $ch = curl_init();
+
+ curl_setopt_array($ch, array(
+ // setup callbacks
+ CURLOPT_READFUNCTION => array($this, 'callbackReadBody'),
+ CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),
+ CURLOPT_WRITEFUNCTION => array($this, 'callbackWriteBody'),
+ // disallow redirects
+ CURLOPT_FOLLOWLOCATION => false,
+ // buffer size
+ CURLOPT_BUFFERSIZE => $this->request->getConfig('buffer_size'),
+ // connection timeout
+ CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),
+ // save full outgoing headers, in case someone is interested
+ CURLINFO_HEADER_OUT => true,
+ // request url
+ CURLOPT_URL => $this->request->getUrl()->getUrl()
+ ));
+
+ // request timeout
+ if ($timeout = $this->request->getConfig('timeout')) {
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+ }
+
+ // set HTTP version
+ switch ($this->request->getConfig('protocol_version')) {
+ case '1.0':
+ curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ break;
+ case '1.1':
+ curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ }
+
+ // set request method
+ switch ($this->request->getMethod()) {
+ case HTTP_Request2::METHOD_GET:
+ curl_setopt($ch, CURLOPT_HTTPGET, true);
+ break;
+ case HTTP_Request2::METHOD_POST:
+ curl_setopt($ch, CURLOPT_POST, true);
+ break;
+ default:
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
+ }
+
+ // set proxy, if needed
+ if ($host = $this->request->getConfig('proxy_host')) {
+ if (!($port = $this->request->getConfig('proxy_port'))) {
+ throw new HTTP_Request2_Exception('Proxy port not provided');
+ }
+ curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);
+ if ($user = $this->request->getConfig('proxy_user')) {
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $user . ':' .
+ $this->request->getConfig('proxy_password'));
+ switch ($this->request->getConfig('proxy_auth_scheme')) {
+ case HTTP_Request2::AUTH_BASIC:
+ curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
+ break;
+ case HTTP_Request2::AUTH_DIGEST:
+ curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
+ }
+ }
+ }
+
+ // set authentication data
+ if ($auth = $this->request->getAuth()) {
+ curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
+ switch ($auth['scheme']) {
+ case HTTP_Request2::AUTH_BASIC:
+ curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
+ break;
+ case HTTP_Request2::AUTH_DIGEST:
+ curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+ }
+ }
+
+ // set SSL options
+ if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) {
+ foreach ($this->request->getConfig() as $name => $value) {
+ if ('ssl_verify_host' == $name && null !== $value) {
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);
+ } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {
+ curl_setopt($ch, self::$sslContextMap[$name], $value);
+ }
+ }
+ }
+
+ $headers = $this->request->getHeaders();
+ // make cURL automagically send proper header
+ if (!isset($headers['accept-encoding'])) {
+ $headers['accept-encoding'] = '';
+ }
+
+ // set headers having special cURL keys
+ foreach (self::$headerMap as $name => $option) {
+ if (isset($headers[$name])) {
+ curl_setopt($ch, $option, $headers[$name]);
+ unset($headers[$name]);
+ }
+ }
+
+ $this->calculateRequestLength($headers);
+
+ // set headers not having special keys
+ $headersFmt = array();
+ foreach ($headers as $name => $value) {
+ $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
+ $headersFmt[] = $canonicalName . ': ' . $value;
+ }
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);
+
+ return $ch;
+ }
+
+ /**
+ * Callback function called by cURL for reading the request body
+ *
+ * @param resource cURL handle
+ * @param resource file descriptor (not used)
+ * @param integer maximum length of data to return
+ * @return string part of the request body, up to $length bytes
+ */
+ protected function callbackReadBody($ch, $fd, $length)
+ {
+ if (!$this->eventSentHeaders) {
+ $this->request->setLastEvent(
+ 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
+ );
+ $this->eventSentHeaders = true;
+ }
+ if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
+ 0 == $this->contentLength || $this->position >= $this->contentLength
+ ) {
+ return '';
+ }
+ if (is_string($this->requestBody)) {
+ $string = substr($this->requestBody, $this->position, $length);
+ } elseif (is_resource($this->requestBody)) {
+ $string = fread($this->requestBody, $length);
+ } else {
+ $string = $this->requestBody->read($length);
+ }
+ $this->request->setLastEvent('sentBodyPart', strlen($string));
+ $this->position += strlen($string);
+ return $string;
+ }
+
+ /**
+ * Callback function called by cURL for saving the response headers
+ *
+ * @param resource cURL handle
+ * @param string response header (with trailing CRLF)
+ * @return integer number of bytes saved
+ * @see HTTP_Request2_Response::parseHeaderLine()
+ */
+ protected function callbackWriteHeader($ch, $string)
+ {
+ // we may receive a second set of headers if doing e.g. digest auth
+ if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {
+ // don't bother with 100-Continue responses (bug #15785)
+ if (!$this->eventSentHeaders ||
+ $this->response->getStatus() >= 200
+ ) {
+ $this->request->setLastEvent(
+ 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
+ );
+ }
+ $this->eventSentHeaders = true;
+ // we'll need a new response object
+ if ($this->eventReceivedHeaders) {
+ $this->eventReceivedHeaders = false;
+ $this->response = null;
+ }
+ }
+ if (empty($this->response)) {
+ $this->response = new HTTP_Request2_Response($string, false);
+ } else {
+ $this->response->parseHeaderLine($string);
+ if ('' == trim($string)) {
+ // don't bother with 100-Continue responses (bug #15785)
+ if (200 <= $this->response->getStatus()) {
+ $this->request->setLastEvent('receivedHeaders', $this->response);
+ }
+ $this->eventReceivedHeaders = true;
+ }
+ }
+ return strlen($string);
+ }
+
+ /**
+ * Callback function called by cURL for saving the response body
+ *
+ * @param resource cURL handle (not used)
+ * @param string part of the response body
+ * @return integer number of bytes saved
+ * @see HTTP_Request2_Response::appendBody()
+ */
+ protected function callbackWriteBody($ch, $string)
+ {
+ // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if
+ // response doesn't start with proper HTTP status line (see bug #15716)
+ if (empty($this->response)) {
+ throw new HTTP_Request2_Exception("Malformed response: {$string}");
+ }
+ if ($this->request->getConfig('store_body')) {
+ $this->response->appendBody($string);
+ }
+ $this->request->setLastEvent('receivedBodyPart', $string);
+ return strlen($string);
+ }
+}
+?>
diff --git a/extlib/HTTP/Request2/Adapter/Mock.php b/extlib/HTTP/Request2/Adapter/Mock.php
new file mode 100644
index 000000000..89688003b
--- /dev/null
+++ b/extlib/HTTP/Request2/Adapter/Mock.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Mock adapter intended for testing
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Mock.php 274406 2009-01-23 18:01:57Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for HTTP_Request2 adapters
+ */
+require_once 'HTTP/Request2/Adapter.php';
+
+/**
+ * Mock adapter intended for testing
+ *
+ * Can be used to test applications depending on HTTP_Request2 package without
+ * actually performing any HTTP requests. This adapter will return responses
+ * previously added via addResponse()
+ * <code>
+ * $mock = new HTTP_Request2_Adapter_Mock();
+ * $mock->addResponse("HTTP/1.1 ... ");
+ *
+ * $request = new HTTP_Request2();
+ * $request->setAdapter($mock);
+ *
+ * // This will return the response set above
+ * $response = $req->send();
+ * </code>
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ */
+class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
+{
+ /**
+ * A queue of responses to be returned by sendRequest()
+ * @var array
+ */
+ protected $responses = array();
+
+ /**
+ * Returns the next response from the queue built by addResponse()
+ *
+ * If the queue is empty will return default empty response with status 400,
+ * if an Exception object was added to the queue it will be thrown.
+ *
+ * @param HTTP_Request2
+ * @return HTTP_Request2_Response
+ * @throws Exception
+ */
+ public function sendRequest(HTTP_Request2 $request)
+ {
+ if (count($this->responses) > 0) {
+ $response = array_shift($this->responses);
+ if ($response instanceof HTTP_Request2_Response) {
+ return $response;
+ } else {
+ // rethrow the exception,
+ $class = get_class($response);
+ $message = $response->getMessage();
+ $code = $response->getCode();
+ throw new $class($message, $code);
+ }
+ } else {
+ return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");
+ }
+ }
+
+ /**
+ * Adds response to the queue
+ *
+ * @param mixed either a string, a pointer to an open file,
+ * a HTTP_Request2_Response or Exception object
+ * @throws HTTP_Request2_Exception
+ */
+ public function addResponse($response)
+ {
+ if (is_string($response)) {
+ $response = self::createResponseFromString($response);
+ } elseif (is_resource($response)) {
+ $response = self::createResponseFromFile($response);
+ } elseif (!$response instanceof HTTP_Request2_Response &&
+ !$response instanceof Exception
+ ) {
+ throw new HTTP_Request2_Exception('Parameter is not a valid response');
+ }
+ $this->responses[] = $response;
+ }
+
+ /**
+ * Creates a new HTTP_Request2_Response object from a string
+ *
+ * @param string
+ * @return HTTP_Request2_Response
+ * @throws HTTP_Request2_Exception
+ */
+ public static function createResponseFromString($str)
+ {
+ $parts = preg_split('!(\r?\n){2}!m', $str, 2);
+ $headerLines = explode("\n", $parts[0]);
+ $response = new HTTP_Request2_Response(array_shift($headerLines));
+ foreach ($headerLines as $headerLine) {
+ $response->parseHeaderLine($headerLine);
+ }
+ $response->parseHeaderLine('');
+ if (isset($parts[1])) {
+ $response->appendBody($parts[1]);
+ }
+ return $response;
+ }
+
+ /**
+ * Creates a new HTTP_Request2_Response object from a file
+ *
+ * @param resource file pointer returned by fopen()
+ * @return HTTP_Request2_Response
+ * @throws HTTP_Request2_Exception
+ */
+ public static function createResponseFromFile($fp)
+ {
+ $response = new HTTP_Request2_Response(fgets($fp));
+ do {
+ $headerLine = fgets($fp);
+ $response->parseHeaderLine($headerLine);
+ } while ('' != trim($headerLine));
+
+ while (!feof($fp)) {
+ $response->appendBody(fread($fp, 8192));
+ }
+ return $response;
+ }
+}
+?> \ No newline at end of file
diff --git a/extlib/HTTP/Request2/Adapter/Socket.php b/extlib/HTTP/Request2/Adapter/Socket.php
new file mode 100644
index 000000000..ff44d4959
--- /dev/null
+++ b/extlib/HTTP/Request2/Adapter/Socket.php
@@ -0,0 +1,971 @@
+<?php
+/**
+ * Socket-based adapter for HTTP_Request2
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Socket.php 279760 2009-05-03 10:46:42Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for HTTP_Request2 adapters
+ */
+require_once 'HTTP/Request2/Adapter.php';
+
+/**
+ * Socket-based adapter for HTTP_Request2
+ *
+ * This adapter uses only PHP sockets and will work on almost any PHP
+ * environment. Code is based on original HTTP_Request PEAR package.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ */
+class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
+{
+ /**
+ * Regular expression for 'token' rule from RFC 2616
+ */
+ const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';
+
+ /**
+ * Regular expression for 'quoted-string' rule from RFC 2616
+ */
+ const REGEXP_QUOTED_STRING = '"(?:\\\\.|[^\\\\"])*"';
+
+ /**
+ * Connected sockets, needed for Keep-Alive support
+ * @var array
+ * @see connect()
+ */
+ protected static $sockets = array();
+
+ /**
+ * Data for digest authentication scheme
+ *
+ * The keys for the array are URL prefixes.
+ *
+ * The values are associative arrays with data (realm, nonce, nonce-count,
+ * opaque...) needed for digest authentication. Stored here to prevent making
+ * duplicate requests to digest-protected resources after we have already
+ * received the challenge.
+ *
+ * @var array
+ */
+ protected static $challenges = array();
+
+ /**
+ * Connected socket
+ * @var resource
+ * @see connect()
+ */
+ protected $socket;
+
+ /**
+ * Challenge used for server digest authentication
+ * @var array
+ */
+ protected $serverChallenge;
+
+ /**
+ * Challenge used for proxy digest authentication
+ * @var array
+ */
+ protected $proxyChallenge;
+
+ /**
+ * Global timeout, exception will be raised if request continues past this time
+ * @var integer
+ */
+ protected $timeout = null;
+
+ /**
+ * Remaining length of the current chunk, when reading chunked response
+ * @var integer
+ * @see readChunked()
+ */
+ protected $chunkLength = 0;
+
+ /**
+ * Sends request to the remote server and returns its response
+ *
+ * @param HTTP_Request2
+ * @return HTTP_Request2_Response
+ * @throws HTTP_Request2_Exception
+ */
+ public function sendRequest(HTTP_Request2 $request)
+ {
+ $this->request = $request;
+ $keepAlive = $this->connect();
+ $headers = $this->prepareHeaders();
+
+ // Use global request timeout if given, see feature requests #5735, #8964
+ if ($timeout = $request->getConfig('timeout')) {
+ $this->timeout = time() + $timeout;
+ } else {
+ $this->timeout = null;
+ }
+
+ try {
+ if (false === @fwrite($this->socket, $headers, strlen($headers))) {
+ throw new HTTP_Request2_Exception('Error writing request');
+ }
+ // provide request headers to the observer, see request #7633
+ $this->request->setLastEvent('sentHeaders', $headers);
+ $this->writeBody();
+
+ if ($this->timeout && time() > $this->timeout) {
+ throw new HTTP_Request2_Exception(
+ 'Request timed out after ' .
+ $request->getConfig('timeout') . ' second(s)'
+ );
+ }
+
+ $response = $this->readResponse();
+
+ if (!$this->canKeepAlive($keepAlive, $response)) {
+ $this->disconnect();
+ }
+
+ if ($this->shouldUseProxyDigestAuth($response)) {
+ return $this->sendRequest($request);
+ }
+ if ($this->shouldUseServerDigestAuth($response)) {
+ return $this->sendRequest($request);
+ }
+ if ($authInfo = $response->getHeader('authentication-info')) {
+ $this->updateChallenge($this->serverChallenge, $authInfo);
+ }
+ if ($proxyInfo = $response->getHeader('proxy-authentication-info')) {
+ $this->updateChallenge($this->proxyChallenge, $proxyInfo);
+ }
+
+ } catch (Exception $e) {
+ $this->disconnect();
+ throw $e;
+ }
+
+ return $response;
+ }
+
+ /**
+ * Connects to the remote server
+ *
+ * @return bool whether the connection can be persistent
+ * @throws HTTP_Request2_Exception
+ */
+ protected function connect()
+ {
+ $secure = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https');
+ $tunnel = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
+ $headers = $this->request->getHeaders();
+ $reqHost = $this->request->getUrl()->getHost();
+ if (!($reqPort = $this->request->getUrl()->getPort())) {
+ $reqPort = $secure? 443: 80;
+ }
+
+ if ($host = $this->request->getConfig('proxy_host')) {
+ if (!($port = $this->request->getConfig('proxy_port'))) {
+ throw new HTTP_Request2_Exception('Proxy port not provided');
+ }
+ $proxy = true;
+ } else {
+ $host = $reqHost;
+ $port = $reqPort;
+ $proxy = false;
+ }
+
+ if ($tunnel && !$proxy) {
+ throw new HTTP_Request2_Exception(
+ "Trying to perform CONNECT request without proxy"
+ );
+ }
+ if ($secure && !in_array('ssl', stream_get_transports())) {
+ throw new HTTP_Request2_Exception(
+ 'Need OpenSSL support for https:// requests'
+ );
+ }
+
+ // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
+ // connection token to a proxy server...
+ if ($proxy && !$secure &&
+ !empty($headers['connection']) && 'Keep-Alive' == $headers['connection']
+ ) {
+ $this->request->setHeader('connection');
+ }
+
+ $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') &&
+ empty($headers['connection'])) ||
+ (!empty($headers['connection']) &&
+ 'Keep-Alive' == $headers['connection']);
+ $host = ((!$secure || $proxy)? 'tcp://': 'ssl://') . $host;
+
+ $options = array();
+ if ($secure || $tunnel) {
+ foreach ($this->request->getConfig() as $name => $value) {
+ if ('ssl_' == substr($name, 0, 4) && null !== $value) {
+ if ('ssl_verify_host' == $name) {
+ if ($value) {
+ $options['CN_match'] = $reqHost;
+ }
+ } else {
+ $options[substr($name, 4)] = $value;
+ }
+ }
+ }
+ ksort($options);
+ }
+
+ // Changing SSL context options after connection is established does *not*
+ // work, we need a new connection if options change
+ $remote = $host . ':' . $port;
+ $socketKey = $remote . (($secure && $proxy)? "->{$reqHost}:{$reqPort}": '') .
+ (empty($options)? '': ':' . serialize($options));
+ unset($this->socket);
+
+ // We use persistent connections and have a connected socket?
+ // Ensure that the socket is still connected, see bug #16149
+ if ($keepAlive && !empty(self::$sockets[$socketKey]) &&
+ !feof(self::$sockets[$socketKey])
+ ) {
+ $this->socket =& self::$sockets[$socketKey];
+
+ } elseif ($secure && $proxy && !$tunnel) {
+ $this->establishTunnel();
+ $this->request->setLastEvent(
+ 'connect', "ssl://{$reqHost}:{$reqPort} via {$host}:{$port}"
+ );
+ self::$sockets[$socketKey] =& $this->socket;
+
+ } else {
+ // Set SSL context options if doing HTTPS request or creating a tunnel
+ $context = stream_context_create();
+ foreach ($options as $name => $value) {
+ if (!stream_context_set_option($context, 'ssl', $name, $value)) {
+ throw new HTTP_Request2_Exception(
+ "Error setting SSL context option '{$name}'"
+ );
+ }
+ }
+ $this->socket = @stream_socket_client(
+ $remote, $errno, $errstr,
+ $this->request->getConfig('connect_timeout'),
+ STREAM_CLIENT_CONNECT, $context
+ );
+ if (!$this->socket) {
+ throw new HTTP_Request2_Exception(
+ "Unable to connect to {$remote}. Error #{$errno}: {$errstr}"
+ );
+ }
+ $this->request->setLastEvent('connect', $remote);
+ self::$sockets[$socketKey] =& $this->socket;
+ }
+ return $keepAlive;
+ }
+
+ /**
+ * Establishes a tunnel to a secure remote server via HTTP CONNECT request
+ *
+ * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP
+ * sees that we are connected to a proxy server (duh!) rather than the server
+ * that presents its certificate.
+ *
+ * @link http://tools.ietf.org/html/rfc2817#section-5.2
+ * @throws HTTP_Request2_Exception
+ */
+ protected function establishTunnel()
+ {
+ $donor = new self;
+ $connect = new HTTP_Request2(
+ $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT,
+ array_merge($this->request->getConfig(),
+ array('adapter' => $donor))
+ );
+ $response = $connect->send();
+ // Need any successful (2XX) response
+ if (200 > $response->getStatus() || 300 <= $response->getStatus()) {
+ throw new HTTP_Request2_Exception(
+ 'Failed to connect via HTTPS proxy. Proxy response: ' .
+ $response->getStatus() . ' ' . $response->getReasonPhrase()
+ );
+ }
+ $this->socket = $donor->socket;
+
+ $modes = array(
+ STREAM_CRYPTO_METHOD_TLS_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv2_CLIENT
+ );
+
+ foreach ($modes as $mode) {
+ if (stream_socket_enable_crypto($this->socket, true, $mode)) {
+ return;
+ }
+ }
+ throw new HTTP_Request2_Exception(
+ 'Failed to enable secure connection when connecting through proxy'
+ );
+ }
+
+ /**
+ * Checks whether current connection may be reused or should be closed
+ *
+ * @param boolean whether connection could be persistent
+ * in the first place
+ * @param HTTP_Request2_Response response object to check
+ * @return boolean
+ */
+ protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)
+ {
+ // Do not close socket on successful CONNECT request
+ if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&
+ 200 <= $response->getStatus() && 300 > $response->getStatus()
+ ) {
+ return true;
+ }
+
+ $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding')) ||
+ null !== $response->getHeader('content-length');
+ $persistent = 'keep-alive' == strtolower($response->getHeader('connection')) ||
+ (null === $response->getHeader('connection') &&
+ '1.1' == $response->getVersion());
+ return $requestKeepAlive && $lengthKnown && $persistent;
+ }
+
+ /**
+ * Disconnects from the remote server
+ */
+ protected function disconnect()
+ {
+ if (is_resource($this->socket)) {
+ fclose($this->socket);
+ $this->socket = null;
+ $this->request->setLastEvent('disconnect');
+ }
+ }
+
+ /**
+ * Checks whether another request should be performed with server digest auth
+ *
+ * Several conditions should be satisfied for it to return true:
+ * - response status should be 401
+ * - auth credentials should be set in the request object
+ * - response should contain WWW-Authenticate header with digest challenge
+ * - there is either no challenge stored for this URL or new challenge
+ * contains stale=true parameter (in other case we probably just failed
+ * due to invalid username / password)
+ *
+ * The method stores challenge values in $challenges static property
+ *
+ * @param HTTP_Request2_Response response to check
+ * @return boolean whether another request should be performed
+ * @throws HTTP_Request2_Exception in case of unsupported challenge parameters
+ */
+ protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response)
+ {
+ // no sense repeating a request if we don't have credentials
+ if (401 != $response->getStatus() || !$this->request->getAuth()) {
+ return false;
+ }
+ if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) {
+ return false;
+ }
+
+ $url = $this->request->getUrl();
+ $scheme = $url->getScheme();
+ $host = $scheme . '://' . $url->getHost();
+ if ($port = $url->getPort()) {
+ if ((0 == strcasecmp($scheme, 'http') && 80 != $port) ||
+ (0 == strcasecmp($scheme, 'https') && 443 != $port)
+ ) {
+ $host .= ':' . $port;
+ }
+ }
+
+ if (!empty($challenge['domain'])) {
+ $prefixes = array();
+ foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) {
+ // don't bother with different servers
+ if ('/' == substr($prefix, 0, 1)) {
+ $prefixes[] = $host . $prefix;
+ }
+ }
+ }
+ if (empty($prefixes)) {
+ $prefixes = array($host . '/');
+ }
+
+ $ret = true;
+ foreach ($prefixes as $prefix) {
+ if (!empty(self::$challenges[$prefix]) &&
+ (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
+ ) {
+ // probably credentials are invalid
+ $ret = false;
+ }
+ self::$challenges[$prefix] =& $challenge;
+ }
+ return $ret;
+ }
+
+ /**
+ * Checks whether another request should be performed with proxy digest auth
+ *
+ * Several conditions should be satisfied for it to return true:
+ * - response status should be 407
+ * - proxy auth credentials should be set in the request object
+ * - response should contain Proxy-Authenticate header with digest challenge
+ * - there is either no challenge stored for this proxy or new challenge
+ * contains stale=true parameter (in other case we probably just failed
+ * due to invalid username / password)
+ *
+ * The method stores challenge values in $challenges static property
+ *
+ * @param HTTP_Request2_Response response to check
+ * @return boolean whether another request should be performed
+ * @throws HTTP_Request2_Exception in case of unsupported challenge parameters
+ */
+ protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response)
+ {
+ if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) {
+ return false;
+ }
+ if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) {
+ return false;
+ }
+
+ $key = 'proxy://' . $this->request->getConfig('proxy_host') .
+ ':' . $this->request->getConfig('proxy_port');
+
+ if (!empty(self::$challenges[$key]) &&
+ (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
+ ) {
+ $ret = false;
+ } else {
+ $ret = true;
+ }
+ self::$challenges[$key] = $challenge;
+ return $ret;
+ }
+
+ /**
+ * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value
+ *
+ * There is a problem with implementation of RFC 2617: several of the parameters
+ * here are defined as quoted-string and thus may contain backslash escaped
+ * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as
+ * just value of quoted-string X without surrounding quotes, it doesn't speak
+ * about removing backslash escaping.
+ *
+ * Now realm parameter is user-defined and human-readable, strange things
+ * happen when it contains quotes:
+ * - Apache allows quotes in realm, but apparently uses realm value without
+ * backslashes for digest computation
+ * - Squid allows (manually escaped) quotes there, but it is impossible to
+ * authorize with either escaped or unescaped quotes used in digest,
+ * probably it can't parse the response (?)
+ * - Both IE and Firefox display realm value with backslashes in
+ * the password popup and apparently use the same value for digest
+ *
+ * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in
+ * quoted-string handling, unfortunately that means failure to authorize
+ * sometimes
+ *
+ * @param string value of WWW-Authenticate or Proxy-Authenticate header
+ * @return mixed associative array with challenge parameters, false if
+ * no challenge is present in header value
+ * @throws HTTP_Request2_Exception in case of unsupported challenge parameters
+ */
+ protected function parseDigestChallenge($headerValue)
+ {
+ $authParam = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
+ self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')';
+ $challenge = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";
+ if (!preg_match($challenge, $headerValue, $matches)) {
+ return false;
+ }
+
+ preg_match_all('!' . $authParam . '!', $matches[0], $params);
+ $paramsAry = array();
+ $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale',
+ 'algorithm', 'qop');
+ for ($i = 0; $i < count($params[0]); $i++) {
+ // section 3.2.1: Any unrecognized directive MUST be ignored.
+ if (in_array($params[1][$i], $knownParams)) {
+ if ('"' == substr($params[2][$i], 0, 1)) {
+ $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
+ } else {
+ $paramsAry[$params[1][$i]] = $params[2][$i];
+ }
+ }
+ }
+ // we only support qop=auth
+ if (!empty($paramsAry['qop']) &&
+ !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))
+ ) {
+ throw new HTTP_Request2_Exception(
+ "Only 'auth' qop is currently supported in digest authentication, " .
+ "server requested '{$paramsAry['qop']}'"
+ );
+ }
+ // we only support algorithm=MD5
+ if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {
+ throw new HTTP_Request2_Exception(
+ "Only 'MD5' algorithm is currently supported in digest authentication, " .
+ "server requested '{$paramsAry['algorithm']}'"
+ );
+ }
+
+ return $paramsAry;
+ }
+
+ /**
+ * Parses [Proxy-]Authentication-Info header value and updates challenge
+ *
+ * @param array challenge to update
+ * @param string value of [Proxy-]Authentication-Info header
+ * @todo validate server rspauth response
+ */
+ protected function updateChallenge(&$challenge, $headerValue)
+ {
+ $authParam = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
+ self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!';
+ $paramsAry = array();
+
+ preg_match_all($authParam, $headerValue, $params);
+ for ($i = 0; $i < count($params[0]); $i++) {
+ if ('"' == substr($params[2][$i], 0, 1)) {
+ $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
+ } else {
+ $paramsAry[$params[1][$i]] = $params[2][$i];
+ }
+ }
+ // for now, just update the nonce value
+ if (!empty($paramsAry['nextnonce'])) {
+ $challenge['nonce'] = $paramsAry['nextnonce'];
+ $challenge['nc'] = 1;
+ }
+ }
+
+ /**
+ * Creates a value for [Proxy-]Authorization header when using digest authentication
+ *
+ * @param string user name
+ * @param string password
+ * @param string request URL
+ * @param array digest challenge parameters
+ * @return string value of [Proxy-]Authorization request header
+ * @link http://tools.ietf.org/html/rfc2617#section-3.2.2
+ */
+ protected function createDigestResponse($user, $password, $url, &$challenge)
+ {
+ if (false !== ($q = strpos($url, '?')) &&
+ $this->request->getConfig('digest_compat_ie')
+ ) {
+ $url = substr($url, 0, $q);
+ }
+
+ $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password);
+ $a2 = md5($this->request->getMethod() . ':' . $url);
+
+ if (empty($challenge['qop'])) {
+ $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);
+ } else {
+ $challenge['cnonce'] = 'Req2.' . rand();
+ if (empty($challenge['nc'])) {
+ $challenge['nc'] = 1;
+ }
+ $nc = sprintf('%08x', $challenge['nc']++);
+ $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' .
+ $challenge['cnonce'] . ':auth:' . $a2);
+ }
+ return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' .
+ 'realm="' . $challenge['realm'] . '", ' .
+ 'nonce="' . $challenge['nonce'] . '", ' .
+ 'uri="' . $url . '", ' .
+ 'response="' . $digest . '"' .
+ (!empty($challenge['opaque'])?
+ ', opaque="' . $challenge['opaque'] . '"':
+ '') .
+ (!empty($challenge['qop'])?
+ ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':
+ '');
+ }
+
+ /**
+ * Adds 'Authorization' header (if needed) to request headers array
+ *
+ * @param array request headers
+ * @param string request host (needed for digest authentication)
+ * @param string request URL (needed for digest authentication)
+ * @throws HTTP_Request2_Exception
+ */
+ protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)
+ {
+ if (!($auth = $this->request->getAuth())) {
+ return;
+ }
+ switch ($auth['scheme']) {
+ case HTTP_Request2::AUTH_BASIC:
+ $headers['authorization'] =
+ 'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']);
+ break;
+
+ case HTTP_Request2::AUTH_DIGEST:
+ unset($this->serverChallenge);
+ $fullUrl = ('/' == $requestUrl[0])?
+ $this->request->getUrl()->getScheme() . '://' .
+ $requestHost . $requestUrl:
+ $requestUrl;
+ foreach (array_keys(self::$challenges) as $key) {
+ if ($key == substr($fullUrl, 0, strlen($key))) {
+ $headers['authorization'] = $this->createDigestResponse(
+ $auth['user'], $auth['password'],
+ $requestUrl, self::$challenges[$key]
+ );
+ $this->serverChallenge =& self::$challenges[$key];
+ break;
+ }
+ }
+ break;
+
+ default:
+ throw new HTTP_Request2_Exception(
+ "Unknown HTTP authentication scheme '{$auth['scheme']}'"
+ );
+ }
+ }
+
+ /**
+ * Adds 'Proxy-Authorization' header (if needed) to request headers array
+ *
+ * @param array request headers
+ * @param string request URL (needed for digest authentication)
+ * @throws HTTP_Request2_Exception
+ */
+ protected function addProxyAuthorizationHeader(&$headers, $requestUrl)
+ {
+ if (!$this->request->getConfig('proxy_host') ||
+ !($user = $this->request->getConfig('proxy_user')) ||
+ (0 == strcasecmp('https', $this->request->getUrl()->getScheme()) &&
+ HTTP_Request2::METHOD_CONNECT != $this->request->getMethod())
+ ) {
+ return;
+ }
+
+ $password = $this->request->getConfig('proxy_password');
+ switch ($this->request->getConfig('proxy_auth_scheme')) {
+ case HTTP_Request2::AUTH_BASIC:
+ $headers['proxy-authorization'] =
+ 'Basic ' . base64_encode($user . ':' . $password);
+ break;
+
+ case HTTP_Request2::AUTH_DIGEST:
+ unset($this->proxyChallenge);
+ $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
+ ':' . $this->request->getConfig('proxy_port');
+ if (!empty(self::$challenges[$proxyUrl])) {
+ $headers['proxy-authorization'] = $this->createDigestResponse(
+ $user, $password,
+ $requestUrl, self::$challenges[$proxyUrl]
+ );
+ $this->proxyChallenge =& self::$challenges[$proxyUrl];
+ }
+ break;
+
+ default:
+ throw new HTTP_Request2_Exception(
+ "Unknown HTTP authentication scheme '" .
+ $this->request->getConfig('proxy_auth_scheme') . "'"
+ );
+ }
+ }
+
+
+ /**
+ * Creates the string with the Request-Line and request headers
+ *
+ * @return string
+ * @throws HTTP_Request2_Exception
+ */
+ protected function prepareHeaders()
+ {
+ $headers = $this->request->getHeaders();
+ $url = $this->request->getUrl();
+ $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
+ $host = $url->getHost();
+
+ $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;
+ if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
+ $host .= ':' . (empty($port)? $defaultPort: $port);
+ }
+ // Do not overwrite explicitly set 'Host' header, see bug #16146
+ if (!isset($headers['host'])) {
+ $headers['host'] = $host;
+ }
+
+ if ($connect) {
+ $requestUrl = $host;
+
+ } else {
+ if (!$this->request->getConfig('proxy_host') ||
+ 0 == strcasecmp($url->getScheme(), 'https')
+ ) {
+ $requestUrl = '';
+ } else {
+ $requestUrl = $url->getScheme() . '://' . $host;
+ }
+ $path = $url->getPath();
+ $query = $url->getQuery();
+ $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
+ }
+
+ if ('1.1' == $this->request->getConfig('protocol_version') &&
+ extension_loaded('zlib') && !isset($headers['accept-encoding'])
+ ) {
+ $headers['accept-encoding'] = 'gzip, deflate';
+ }
+
+ $this->addAuthorizationHeader($headers, $host, $requestUrl);
+ $this->addProxyAuthorizationHeader($headers, $requestUrl);
+ $this->calculateRequestLength($headers);
+
+ $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .
+ $this->request->getConfig('protocol_version') . "\r\n";
+ foreach ($headers as $name => $value) {
+ $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
+ $headersStr .= $canonicalName . ': ' . $value . "\r\n";
+ }
+ return $headersStr . "\r\n";
+ }
+
+ /**
+ * Sends the request body
+ *
+ * @throws HTTP_Request2_Exception
+ */
+ protected function writeBody()
+ {
+ if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
+ 0 == $this->contentLength
+ ) {
+ return;
+ }
+
+ $position = 0;
+ $bufferSize = $this->request->getConfig('buffer_size');
+ while ($position < $this->contentLength) {
+ if (is_string($this->requestBody)) {
+ $str = substr($this->requestBody, $position, $bufferSize);
+ } elseif (is_resource($this->requestBody)) {
+ $str = fread($this->requestBody, $bufferSize);
+ } else {
+ $str = $this->requestBody->read($bufferSize);
+ }
+ if (false === @fwrite($this->socket, $str, strlen($str))) {
+ throw new HTTP_Request2_Exception('Error writing request');
+ }
+ // Provide the length of written string to the observer, request #7630
+ $this->request->setLastEvent('sentBodyPart', strlen($str));
+ $position += strlen($str);
+ }
+ }
+
+ /**
+ * Reads the remote server's response
+ *
+ * @return HTTP_Request2_Response
+ * @throws HTTP_Request2_Exception
+ */
+ protected function readResponse()
+ {
+ $bufferSize = $this->request->getConfig('buffer_size');
+
+ do {
+ $response = new HTTP_Request2_Response($this->readLine($bufferSize), true);
+ do {
+ $headerLine = $this->readLine($bufferSize);
+ $response->parseHeaderLine($headerLine);
+ } while ('' != $headerLine);
+ } while (in_array($response->getStatus(), array(100, 101)));
+
+ $this->request->setLastEvent('receivedHeaders', $response);
+
+ // No body possible in such responses
+ if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod() ||
+ (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&
+ 200 <= $response->getStatus() && 300 > $response->getStatus()) ||
+ in_array($response->getStatus(), array(204, 304))
+ ) {
+ return $response;
+ }
+
+ $chunked = 'chunked' == $response->getHeader('transfer-encoding');
+ $length = $response->getHeader('content-length');
+ $hasBody = false;
+ if ($chunked || null === $length || 0 < intval($length)) {
+ // RFC 2616, section 4.4:
+ // 3. ... If a message is received with both a
+ // Transfer-Encoding header field and a Content-Length header field,
+ // the latter MUST be ignored.
+ $toRead = ($chunked || null === $length)? null: $length;
+ $this->chunkLength = 0;
+
+ while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) {
+ if ($chunked) {
+ $data = $this->readChunked($bufferSize);
+ } elseif (is_null($toRead)) {
+ $data = $this->fread($bufferSize);
+ } else {
+ $data = $this->fread(min($toRead, $bufferSize));
+ $toRead -= strlen($data);
+ }
+ if ('' == $data && (!$this->chunkLength || feof($this->socket))) {
+ break;
+ }
+
+ $hasBody = true;
+ if ($this->request->getConfig('store_body')) {
+ $response->appendBody($data);
+ }
+ if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {
+ $this->request->setLastEvent('receivedEncodedBodyPart', $data);
+ } else {
+ $this->request->setLastEvent('receivedBodyPart', $data);
+ }
+ }
+ }
+
+ if ($hasBody) {
+ $this->request->setLastEvent('receivedBody', $response);
+ }
+ return $response;
+ }
+
+ /**
+ * Reads until either the end of the socket or a newline, whichever comes first
+ *
+ * Strips the trailing newline from the returned data, handles global
+ * request timeout. Method idea borrowed from Net_Socket PEAR package.
+ *
+ * @param int buffer size to use for reading
+ * @return Available data up to the newline (not including newline)
+ * @throws HTTP_Request2_Exception In case of timeout
+ */
+ protected function readLine($bufferSize)
+ {
+ $line = '';
+ while (!feof($this->socket)) {
+ if ($this->timeout) {
+ stream_set_timeout($this->socket, max($this->timeout - time(), 1));
+ }
+ $line .= @fgets($this->socket, $bufferSize);
+ $info = stream_get_meta_data($this->socket);
+ if ($info['timed_out'] || $this->timeout && time() > $this->timeout) {
+ throw new HTTP_Request2_Exception(
+ 'Request timed out after ' .
+ $this->request->getConfig('timeout') . ' second(s)'
+ );
+ }
+ if (substr($line, -1) == "\n") {
+ return rtrim($line, "\r\n");
+ }
+ }
+ return $line;
+ }
+
+ /**
+ * Wrapper around fread(), handles global request timeout
+ *
+ * @param int Reads up to this number of bytes
+ * @return Data read from socket
+ * @throws HTTP_Request2_Exception In case of timeout
+ */
+ protected function fread($length)
+ {
+ if ($this->timeout) {
+ stream_set_timeout($this->socket, max($this->timeout - time(), 1));
+ }
+ $data = fread($this->socket, $length);
+ $info = stream_get_meta_data($this->socket);
+ if ($info['timed_out'] || $this->timeout && time() > $this->timeout) {
+ throw new HTTP_Request2_Exception(
+ 'Request timed out after ' .
+ $this->request->getConfig('timeout') . ' second(s)'
+ );
+ }
+ return $data;
+ }
+
+ /**
+ * Reads a part of response body encoded with chunked Transfer-Encoding
+ *
+ * @param int buffer size to use for reading
+ * @return string
+ * @throws HTTP_Request2_Exception
+ */
+ protected function readChunked($bufferSize)
+ {
+ // at start of the next chunk?
+ if (0 == $this->chunkLength) {
+ $line = $this->readLine($bufferSize);
+ if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
+ throw new HTTP_Request2_Exception(
+ "Cannot decode chunked response, invalid chunk length '{$line}'"
+ );
+ } else {
+ $this->chunkLength = hexdec($matches[1]);
+ // Chunk with zero length indicates the end
+ if (0 == $this->chunkLength) {
+ $this->readLine($bufferSize);
+ return '';
+ }
+ }
+ }
+ $data = $this->fread(min($this->chunkLength, $bufferSize));
+ $this->chunkLength -= strlen($data);
+ if (0 == $this->chunkLength) {
+ $this->readLine($bufferSize); // Trailing CRLF
+ }
+ return $data;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/HTTP/Request2/Exception.php b/extlib/HTTP/Request2/Exception.php
new file mode 100644
index 000000000..bfef7d6c2
--- /dev/null
+++ b/extlib/HTTP/Request2/Exception.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Exception class for HTTP_Request2 package
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Exception.php 273003 2009-01-07 19:28:22Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for exceptions in PEAR
+ */
+require_once 'PEAR/Exception.php';
+
+/**
+ * Exception class for HTTP_Request2 package
+ *
+ * Such a class is required by the Exception RFC:
+ * http://pear.php.net/pepr/pepr-proposal-show.php?id=132
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @version Release: 0.4.1
+ */
+class HTTP_Request2_Exception extends PEAR_Exception
+{
+}
+?> \ No newline at end of file
diff --git a/extlib/HTTP/Request2/MultipartBody.php b/extlib/HTTP/Request2/MultipartBody.php
new file mode 100644
index 000000000..d8afd8344
--- /dev/null
+++ b/extlib/HTTP/Request2/MultipartBody.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Helper class for building multipart/form-data request body
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: MultipartBody.php 287306 2009-08-14 15:22:52Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Class for building multipart/form-data request body
+ *
+ * The class helps to reduce memory consumption by streaming large file uploads
+ * from disk, it also allows monitoring of upload progress (see request #7630)
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ * @link http://tools.ietf.org/html/rfc1867
+ */
+class HTTP_Request2_MultipartBody
+{
+ /**
+ * MIME boundary
+ * @var string
+ */
+ private $_boundary;
+
+ /**
+ * Form parameters added via {@link HTTP_Request2::addPostParameter()}
+ * @var array
+ */
+ private $_params = array();
+
+ /**
+ * File uploads added via {@link HTTP_Request2::addUpload()}
+ * @var array
+ */
+ private $_uploads = array();
+
+ /**
+ * Header for parts with parameters
+ * @var string
+ */
+ private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";
+
+ /**
+ * Header for parts with uploads
+ * @var string
+ */
+ private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";
+
+ /**
+ * Current position in parameter and upload arrays
+ *
+ * First number is index of "current" part, second number is position within
+ * "current" part
+ *
+ * @var array
+ */
+ private $_pos = array(0, 0);
+
+
+ /**
+ * Constructor. Sets the arrays with POST data.
+ *
+ * @param array values of form fields set via {@link HTTP_Request2::addPostParameter()}
+ * @param array file uploads set via {@link HTTP_Request2::addUpload()}
+ * @param bool whether to append brackets to array variable names
+ */
+ public function __construct(array $params, array $uploads, $useBrackets = true)
+ {
+ $this->_params = self::_flattenArray('', $params, $useBrackets);
+ foreach ($uploads as $fieldName => $f) {
+ if (!is_array($f['fp'])) {
+ $this->_uploads[] = $f + array('name' => $fieldName);
+ } else {
+ for ($i = 0; $i < count($f['fp']); $i++) {
+ $upload = array(
+ 'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName)
+ );
+ foreach (array('fp', 'filename', 'size', 'type') as $key) {
+ $upload[$key] = $f[$key][$i];
+ }
+ $this->_uploads[] = $upload;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the length of the body to use in Content-Length header
+ *
+ * @return integer
+ */
+ public function getLength()
+ {
+ $boundaryLength = strlen($this->getBoundary());
+ $headerParamLength = strlen($this->_headerParam) - 4 + $boundaryLength;
+ $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength;
+ $length = $boundaryLength + 6;
+ foreach ($this->_params as $p) {
+ $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2;
+ }
+ foreach ($this->_uploads as $u) {
+ $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) +
+ strlen($u['filename']) + $u['size'] + 2;
+ }
+ return $length;
+ }
+
+ /**
+ * Returns the boundary to use in Content-Type header
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ if (empty($this->_boundary)) {
+ $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime());
+ }
+ return $this->_boundary;
+ }
+
+ /**
+ * Returns next chunk of request body
+ *
+ * @param integer Amount of bytes to read
+ * @return string Up to $length bytes of data, empty string if at end
+ */
+ public function read($length)
+ {
+ $ret = '';
+ $boundary = $this->getBoundary();
+ $paramCount = count($this->_params);
+ $uploadCount = count($this->_uploads);
+ while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {
+ $oldLength = $length;
+ if ($this->_pos[0] < $paramCount) {
+ $param = sprintf($this->_headerParam, $boundary,
+ $this->_params[$this->_pos[0]][0]) .
+ $this->_params[$this->_pos[0]][1] . "\r\n";
+ $ret .= substr($param, $this->_pos[1], $length);
+ $length -= min(strlen($param) - $this->_pos[1], $length);
+
+ } elseif ($this->_pos[0] < $paramCount + $uploadCount) {
+ $pos = $this->_pos[0] - $paramCount;
+ $header = sprintf($this->_headerUpload, $boundary,
+ $this->_uploads[$pos]['name'],
+ $this->_uploads[$pos]['filename'],
+ $this->_uploads[$pos]['type']);
+ if ($this->_pos[1] < strlen($header)) {
+ $ret .= substr($header, $this->_pos[1], $length);
+ $length -= min(strlen($header) - $this->_pos[1], $length);
+ }
+ $filePos = max(0, $this->_pos[1] - strlen($header));
+ if ($length > 0 && $filePos < $this->_uploads[$pos]['size']) {
+ $ret .= fread($this->_uploads[$pos]['fp'], $length);
+ $length -= min($length, $this->_uploads[$pos]['size'] - $filePos);
+ }
+ if ($length > 0) {
+ $start = $this->_pos[1] + ($oldLength - $length) -
+ strlen($header) - $this->_uploads[$pos]['size'];
+ $ret .= substr("\r\n", $start, $length);
+ $length -= min(2 - $start, $length);
+ }
+
+ } else {
+ $closing = '--' . $boundary . "--\r\n";
+ $ret .= substr($closing, $this->_pos[1], $length);
+ $length -= min(strlen($closing) - $this->_pos[1], $length);
+ }
+ if ($length > 0) {
+ $this->_pos = array($this->_pos[0] + 1, 0);
+ } else {
+ $this->_pos[1] += $oldLength;
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Sets the current position to the start of the body
+ *
+ * This allows reusing the same body in another request
+ */
+ public function rewind()
+ {
+ $this->_pos = array(0, 0);
+ foreach ($this->_uploads as $u) {
+ rewind($u['fp']);
+ }
+ }
+
+ /**
+ * Returns the body as string
+ *
+ * Note that it reads all file uploads into memory so it is a good idea not
+ * to use this method with large file uploads and rely on read() instead.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $this->rewind();
+ return $this->read($this->getLength());
+ }
+
+
+ /**
+ * Helper function to change the (probably multidimensional) associative array
+ * into the simple one.
+ *
+ * @param string name for item
+ * @param mixed item's values
+ * @param bool whether to append [] to array variables' names
+ * @return array array with the following items: array('item name', 'item value');
+ */
+ private static function _flattenArray($name, $values, $useBrackets)
+ {
+ if (!is_array($values)) {
+ return array(array($name, $values));
+ } else {
+ $ret = array();
+ foreach ($values as $k => $v) {
+ if (empty($name)) {
+ $newName = $k;
+ } elseif ($useBrackets) {
+ $newName = $name . '[' . $k . ']';
+ } else {
+ $newName = $name;
+ }
+ $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets));
+ }
+ return $ret;
+ }
+ }
+}
+?>
diff --git a/extlib/HTTP/Request2/Observer/Log.php b/extlib/HTTP/Request2/Observer/Log.php
new file mode 100644
index 000000000..b1a055278
--- /dev/null
+++ b/extlib/HTTP/Request2/Observer/Log.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * An observer useful for debugging / testing.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author David Jean Louis <izi@php.net>
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Log.php 272593 2009-01-02 16:27:14Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Exception class for HTTP_Request2 package
+ */
+require_once 'HTTP/Request2/Exception.php';
+
+/**
+ * A debug observer useful for debugging / testing.
+ *
+ * This observer logs to a log target data corresponding to the various request
+ * and response events, it logs by default to php://output but can be configured
+ * to log to a file or via the PEAR Log package.
+ *
+ * A simple example:
+ * <code>
+ * require_once 'HTTP/Request2.php';
+ * require_once 'HTTP/Request2/Observer/Log.php';
+ *
+ * $request = new HTTP_Request2('http://www.example.com');
+ * $observer = new HTTP_Request2_Observer_Log();
+ * $request->attach($observer);
+ * $request->send();
+ * </code>
+ *
+ * A more complex example with PEAR Log:
+ * <code>
+ * require_once 'HTTP/Request2.php';
+ * require_once 'HTTP/Request2/Observer/Log.php';
+ * require_once 'Log.php';
+ *
+ * $request = new HTTP_Request2('http://www.example.com');
+ * // we want to log with PEAR log
+ * $observer = new HTTP_Request2_Observer_Log(Log::factory('console'));
+ *
+ * // we only want to log received headers
+ * $observer->events = array('receivedHeaders');
+ *
+ * $request->attach($observer);
+ * $request->send();
+ * </code>
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author David Jean Louis <izi@php.net>
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: 0.4.1
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+class HTTP_Request2_Observer_Log implements SplObserver
+{
+ // properties {{{
+
+ /**
+ * The log target, it can be a a resource or a PEAR Log instance.
+ *
+ * @var resource|Log $target
+ */
+ protected $target = null;
+
+ /**
+ * The events to log.
+ *
+ * @var array $events
+ */
+ public $events = array(
+ 'connect',
+ 'sentHeaders',
+ 'sentBodyPart',
+ 'receivedHeaders',
+ 'receivedBody',
+ 'disconnect',
+ );
+
+ // }}}
+ // __construct() {{{
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $target Can be a file path (default: php://output), a resource,
+ * or an instance of the PEAR Log class.
+ * @param array $events Array of events to listen to (default: all events)
+ *
+ * @return void
+ */
+ public function __construct($target = 'php://output', array $events = array())
+ {
+ if (!empty($events)) {
+ $this->events = $events;
+ }
+ if (is_resource($target) || $target instanceof Log) {
+ $this->target = $target;
+ } elseif (false === ($this->target = @fopen($target, 'w'))) {
+ throw new HTTP_Request2_Exception("Unable to open '{$target}'");
+ }
+ }
+
+ // }}}
+ // update() {{{
+
+ /**
+ * Called when the request notify us of an event.
+ *
+ * @param HTTP_Request2 $subject The HTTP_Request2 instance
+ *
+ * @return void
+ */
+ public function update(SplSubject $subject)
+ {
+ $event = $subject->getLastEvent();
+ if (!in_array($event['name'], $this->events)) {
+ return;
+ }
+
+ switch ($event['name']) {
+ case 'connect':
+ $this->log('* Connected to ' . $event['data']);
+ break;
+ case 'sentHeaders':
+ $headers = explode("\r\n", $event['data']);
+ array_pop($headers);
+ foreach ($headers as $header) {
+ $this->log('> ' . $header);
+ }
+ break;
+ case 'sentBodyPart':
+ $this->log('> ' . $event['data']);
+ break;
+ case 'receivedHeaders':
+ $this->log(sprintf('< HTTP/%s %s %s',
+ $event['data']->getVersion(),
+ $event['data']->getStatus(),
+ $event['data']->getReasonPhrase()));
+ $headers = $event['data']->getHeader();
+ foreach ($headers as $key => $val) {
+ $this->log('< ' . $key . ': ' . $val);
+ }
+ $this->log('< ');
+ break;
+ case 'receivedBody':
+ $this->log($event['data']->getBody());
+ break;
+ case 'disconnect':
+ $this->log('* Disconnected');
+ break;
+ }
+ }
+
+ // }}}
+ // log() {{{
+
+ /**
+ * Log the given message to the configured target.
+ *
+ * @param string $message Message to display
+ *
+ * @return void
+ */
+ protected function log($message)
+ {
+ if ($this->target instanceof Log) {
+ $this->target->debug($message);
+ } elseif (is_resource($this->target)) {
+ fwrite($this->target, $message . "\r\n");
+ }
+ }
+
+ // }}}
+}
+
+?> \ No newline at end of file
diff --git a/extlib/HTTP/Request2/Response.php b/extlib/HTTP/Request2/Response.php
new file mode 100644
index 000000000..c7c1021fb
--- /dev/null
+++ b/extlib/HTTP/Request2/Response.php
@@ -0,0 +1,549 @@
+<?php
+/**
+ * Class representing a HTTP response
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The names of the authors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Response.php 287948 2009-09-01 17:12:18Z avb $
+ * @link http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Exception class for HTTP_Request2 package
+ */
+require_once 'HTTP/Request2/Exception.php';
+
+/**
+ * Class representing a HTTP response
+ *
+ * The class is designed to be used in "streaming" scenario, building the
+ * response as it is being received:
+ * <code>
+ * $statusLine = read_status_line();
+ * $response = new HTTP_Request2_Response($statusLine);
+ * do {
+ * $headerLine = read_header_line();
+ * $response->parseHeaderLine($headerLine);
+ * } while ($headerLine != '');
+ *
+ * while ($chunk = read_body()) {
+ * $response->appendBody($chunk);
+ * }
+ *
+ * var_dump($response->getHeader(), $response->getCookies(), $response->getBody());
+ * </code>
+ *
+ *
+ * @category HTTP
+ * @package HTTP_Request2
+ * @author Alexey Borzov <avb@php.net>
+ * @version Release: 0.4.1
+ * @link http://tools.ietf.org/html/rfc2616#section-6
+ */
+class HTTP_Request2_Response
+{
+ /**
+ * HTTP protocol version (e.g. 1.0, 1.1)
+ * @var string
+ */
+ protected $version;
+
+ /**
+ * Status code
+ * @var integer
+ * @link http://tools.ietf.org/html/rfc2616#section-6.1.1
+ */
+ protected $code;
+
+ /**
+ * Reason phrase
+ * @var string
+ * @link http://tools.ietf.org/html/rfc2616#section-6.1.1
+ */
+ protected $reasonPhrase;
+
+ /**
+ * Associative array of response headers
+ * @var array
+ */
+ protected $headers = array();
+
+ /**
+ * Cookies set in the response
+ * @var array
+ */
+ protected $cookies = array();
+
+ /**
+ * Name of last header processed by parseHederLine()
+ *
+ * Used to handle the headers that span multiple lines
+ *
+ * @var string
+ */
+ protected $lastHeader = null;
+
+ /**
+ * Response body
+ * @var string
+ */
+ protected $body = '';
+
+ /**
+ * Whether the body is still encoded by Content-Encoding
+ *
+ * cURL provides the decoded body to the callback; if we are reading from
+ * socket the body is still gzipped / deflated
+ *
+ * @var bool
+ */
+ protected $bodyEncoded;
+
+ /**
+ * Associative array of HTTP status code / reason phrase.
+ *
+ * @var array
+ * @link http://tools.ietf.org/html/rfc2616#section-10
+ */
+ protected static $phrases = array(
+
+ // 1xx: Informational - Request received, continuing process
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+
+ // 2xx: Success - The action was successfully received, understood and
+ // accepted
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+
+ // 3xx: Redirection - Further action must be taken in order to complete
+ // the request
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found', // 1.1
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+
+ // 4xx: Client Error - The request contains bad syntax or cannot be
+ // fulfilled
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+
+ // 5xx: Server Error - The server failed to fulfill an apparently
+ // valid request
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 509 => 'Bandwidth Limit Exceeded',
+
+ );
+
+ /**
+ * Constructor, parses the response status line
+ *
+ * @param string Response status line (e.g. "HTTP/1.1 200 OK")
+ * @param bool Whether body is still encoded by Content-Encoding
+ * @throws HTTP_Request2_Exception if status line is invalid according to spec
+ */
+ public function __construct($statusLine, $bodyEncoded = true)
+ {
+ if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {
+ throw new HTTP_Request2_Exception("Malformed response: {$statusLine}");
+ }
+ $this->version = $m[1];
+ $this->code = intval($m[2]);
+ if (!empty($m[3])) {
+ $this->reasonPhrase = trim($m[3]);
+ } elseif (!empty(self::$phrases[$this->code])) {
+ $this->reasonPhrase = self::$phrases[$this->code];
+ }
+ $this->bodyEncoded = (bool)$bodyEncoded;
+ }
+
+ /**
+ * Parses the line from HTTP response filling $headers array
+ *
+ * The method should be called after reading the line from socket or receiving
+ * it into cURL callback. Passing an empty string here indicates the end of
+ * response headers and triggers additional processing, so be sure to pass an
+ * empty string in the end.
+ *
+ * @param string Line from HTTP response
+ */
+ public function parseHeaderLine($headerLine)
+ {
+ $headerLine = trim($headerLine, "\r\n");
+
+ // empty string signals the end of headers, process the received ones
+ if ('' == $headerLine) {
+ if (!empty($this->headers['set-cookie'])) {
+ $cookies = is_array($this->headers['set-cookie'])?
+ $this->headers['set-cookie']:
+ array($this->headers['set-cookie']);
+ foreach ($cookies as $cookieString) {
+ $this->parseCookie($cookieString);
+ }
+ unset($this->headers['set-cookie']);
+ }
+ foreach (array_keys($this->headers) as $k) {
+ if (is_array($this->headers[$k])) {
+ $this->headers[$k] = implode(', ', $this->headers[$k]);
+ }
+ }
+
+ // string of the form header-name: header value
+ } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) {
+ $name = strtolower($m[1]);
+ $value = trim($m[2]);
+ if (empty($this->headers[$name])) {
+ $this->headers[$name] = $value;
+ } else {
+ if (!is_array($this->headers[$name])) {
+ $this->headers[$name] = array($this->headers[$name]);
+ }
+ $this->headers[$name][] = $value;
+ }
+ $this->lastHeader = $name;
+
+ // string
+ } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {
+ if (!is_array($this->headers[$this->lastHeader])) {
+ $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);
+ } else {
+ $key = count($this->headers[$this->lastHeader]) - 1;
+ $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);
+ }
+ }
+ }
+
+ /**
+ * Parses a Set-Cookie header to fill $cookies array
+ *
+ * @param string value of Set-Cookie header
+ * @link http://cgi.netscape.com/newsref/std/cookie_spec.html
+ */
+ protected function parseCookie($cookieString)
+ {
+ $cookie = array(
+ 'expires' => null,
+ 'domain' => null,
+ 'path' => null,
+ 'secure' => false
+ );
+
+ // Only a name=value pair
+ if (!strpos($cookieString, ';')) {
+ $pos = strpos($cookieString, '=');
+ $cookie['name'] = trim(substr($cookieString, 0, $pos));
+ $cookie['value'] = trim(substr($cookieString, $pos + 1));
+
+ // Some optional parameters are supplied
+ } else {
+ $elements = explode(';', $cookieString);
+ $pos = strpos($elements[0], '=');
+ $cookie['name'] = trim(substr($elements[0], 0, $pos));
+ $cookie['value'] = trim(substr($elements[0], $pos + 1));
+
+ for ($i = 1; $i < count($elements); $i++) {
+ if (false === strpos($elements[$i], '=')) {
+ $elName = trim($elements[$i]);
+ $elValue = null;
+ } else {
+ list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
+ }
+ $elName = strtolower($elName);
+ if ('secure' == $elName) {
+ $cookie['secure'] = true;
+ } elseif ('expires' == $elName) {
+ $cookie['expires'] = str_replace('"', '', $elValue);
+ } elseif ('path' == $elName || 'domain' == $elName) {
+ $cookie[$elName] = urldecode($elValue);
+ } else {
+ $cookie[$elName] = $elValue;
+ }
+ }
+ }
+ $this->cookies[] = $cookie;
+ }
+
+ /**
+ * Appends a string to the response body
+ * @param string
+ */
+ public function appendBody($bodyChunk)
+ {
+ $this->body .= $bodyChunk;
+ }
+
+ /**
+ * Returns the status code
+ * @return integer
+ */
+ public function getStatus()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Returns the reason phrase
+ * @return string
+ */
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ /**
+ * Returns either the named header or all response headers
+ *
+ * @param string Name of header to return
+ * @return string|array Value of $headerName header (null if header is
+ * not present), array of all response headers if
+ * $headerName is null
+ */
+ public function getHeader($headerName = null)
+ {
+ if (null === $headerName) {
+ return $this->headers;
+ } else {
+ $headerName = strtolower($headerName);
+ return isset($this->headers[$headerName])? $this->headers[$headerName]: null;
+ }
+ }
+
+ /**
+ * Returns cookies set in response
+ *
+ * @return array
+ */
+ public function getCookies()
+ {
+ return $this->cookies;
+ }
+
+ /**
+ * Returns the body of the response
+ *
+ * @return string
+ * @throws HTTP_Request2_Exception if body cannot be decoded
+ */
+ public function getBody()
+ {
+ if (!$this->bodyEncoded ||
+ !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))
+ ) {
+ return $this->body;
+
+ } else {
+ if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
+ $oldEncoding = mb_internal_encoding();
+ mb_internal_encoding('iso-8859-1');
+ }
+
+ try {
+ switch (strtolower($this->getHeader('content-encoding'))) {
+ case 'gzip':
+ $decoded = self::decodeGzip($this->body);
+ break;
+ case 'deflate':
+ $decoded = self::decodeDeflate($this->body);
+ }
+ } catch (Exception $e) {
+ }
+
+ if (!empty($oldEncoding)) {
+ mb_internal_encoding($oldEncoding);
+ }
+ if (!empty($e)) {
+ throw $e;
+ }
+ return $decoded;
+ }
+ }
+
+ /**
+ * Get the HTTP version of the response
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Decodes the message-body encoded by gzip
+ *
+ * The real decoding work is done by gzinflate() built-in function, this
+ * method only parses the header and checks data for compliance with
+ * RFC 1952
+ *
+ * @param string gzip-encoded data
+ * @return string decoded data
+ * @throws HTTP_Request2_Exception
+ * @link http://tools.ietf.org/html/rfc1952
+ */
+ public static function decodeGzip($data)
+ {
+ $length = strlen($data);
+ // If it doesn't look like gzip-encoded data, don't bother
+ if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
+ return $data;
+ }
+ if (!function_exists('gzinflate')) {
+ throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');
+ }
+ $method = ord(substr($data, 2, 1));
+ if (8 != $method) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: unknown compression method');
+ }
+ $flags = ord(substr($data, 3, 1));
+ if ($flags & 224) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: reserved bits are set');
+ }
+
+ // header is 10 bytes minimum. may be longer, though.
+ $headerLength = 10;
+ // extra fields, need to skip 'em
+ if ($flags & 4) {
+ if ($length - $headerLength - 2 < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $extraLength = unpack('v', substr($data, 10, 2));
+ if ($length - $headerLength - 2 - $extraLength[1] < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $headerLength += $extraLength[1] + 2;
+ }
+ // file name, need to skip that
+ if ($flags & 8) {
+ if ($length - $headerLength - 1 < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $filenameLength = strpos(substr($data, $headerLength), chr(0));
+ if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $headerLength += $filenameLength + 1;
+ }
+ // comment, need to skip that also
+ if ($flags & 16) {
+ if ($length - $headerLength - 1 < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $commentLength = strpos(substr($data, $headerLength), chr(0));
+ if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $headerLength += $commentLength + 1;
+ }
+ // have a CRC for header. let's check
+ if ($flags & 2) {
+ if ($length - $headerLength - 2 < 8) {
+ throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');
+ }
+ $crcReal = 0xffff & crc32(substr($data, 0, $headerLength));
+ $crcStored = unpack('v', substr($data, $headerLength, 2));
+ if ($crcReal != $crcStored[1]) {
+ throw new HTTP_Request2_Exception('Header CRC check failed');
+ }
+ $headerLength += 2;
+ }
+ // unpacked data CRC and size at the end of encoded data
+ $tmp = unpack('V2', substr($data, -8));
+ $dataCrc = $tmp[1];
+ $dataSize = $tmp[2];
+
+ // finally, call the gzinflate() function
+ // don't pass $dataSize to gzinflate, see bugs #13135, #14370
+ $unpacked = gzinflate(substr($data, $headerLength, -8));
+ if (false === $unpacked) {
+ throw new HTTP_Request2_Exception('gzinflate() call failed');
+ } elseif ($dataSize != strlen($unpacked)) {
+ throw new HTTP_Request2_Exception('Data size check failed');
+ } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
+ throw new HTTP_Request2_Exception('Data CRC check failed');
+ }
+ return $unpacked;
+ }
+
+ /**
+ * Decodes the message-body encoded by deflate
+ *
+ * @param string deflate-encoded data
+ * @return string decoded data
+ * @throws HTTP_Request2_Exception
+ */
+ public static function decodeDeflate($data)
+ {
+ if (!function_exists('gzuncompress')) {
+ throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');
+ }
+ // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,
+ // while many applications send raw deflate stream from RFC 1951.
+ // We should check for presence of zlib header and use gzuncompress() or
+ // gzinflate() as needed. See bug #15305
+ $header = unpack('n', substr($data, 0, 2));
+ return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data);
+ }
+}
+?> \ No newline at end of file
diff --git a/extlib/Net/LDAP2.php b/extlib/Net/LDAP2.php
new file mode 100644
index 000000000..26f5e7560
--- /dev/null
+++ b/extlib/Net/LDAP2.php
@@ -0,0 +1,1791 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2 interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @author Jan Wagner <wagner@netsols.de>
+* @author Del <del@babel.com.au>
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: LDAP2.php 286788 2009-08-04 06:05:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Package includes.
+*/
+require_once 'PEAR.php';
+require_once 'Net/LDAP2/RootDSE.php';
+require_once 'Net/LDAP2/Schema.php';
+require_once 'Net/LDAP2/Entry.php';
+require_once 'Net/LDAP2/Search.php';
+require_once 'Net/LDAP2/Util.php';
+require_once 'Net/LDAP2/Filter.php';
+require_once 'Net/LDAP2/LDIF.php';
+require_once 'Net/LDAP2/SchemaCache.interface.php';
+require_once 'Net/LDAP2/SimpleFileSchemaCache.php';
+
+/**
+* Error constants for errors that are not LDAP errors.
+*/
+define('NET_LDAP2_ERROR', 1000);
+
+/**
+* Net_LDAP2 Version
+*/
+define('NET_LDAP2_VERSION', '2.0.7');
+
+/**
+* Net_LDAP2 - manipulate LDAP servers the right way!
+*
+* @category Net
+* @package Net_LDAP2
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @author Jan Wagner <wagner@netsols.de>
+* @author Del <del@babel.com.au>
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+class Net_LDAP2 extends PEAR
+{
+ /**
+ * Class configuration array
+ *
+ * host = the ldap host to connect to
+ * (may be an array of several hosts to try)
+ * port = the server port
+ * version = ldap version (defaults to v 3)
+ * starttls = when set, ldap_start_tls() is run after connecting.
+ * bindpw = no explanation needed
+ * binddn = the DN to bind as.
+ * basedn = ldap base
+ * options = hash of ldap options to set (opt => val)
+ * filter = default search filter
+ * scope = default search scope
+ *
+ * Newly added in 2.0.0RC4, for auto-reconnect:
+ * auto_reconnect = if set to true then the class will automatically
+ * attempt to reconnect to the LDAP server in certain
+ * failure conditionswhen attempting a search, or other
+ * LDAP operation. Defaults to false. Note that if you
+ * set this to true, calls to search() may block
+ * indefinitely if there is a catastrophic server failure.
+ * min_backoff = minimum reconnection delay period (in seconds).
+ * current_backoff = initial reconnection delay period (in seconds).
+ * max_backoff = maximum reconnection delay period (in seconds).
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_config = array('host' => 'localhost',
+ 'port' => 389,
+ 'version' => 3,
+ 'starttls' => false,
+ 'binddn' => '',
+ 'bindpw' => '',
+ 'basedn' => '',
+ 'options' => array(),
+ 'filter' => '(objectClass=*)',
+ 'scope' => 'sub',
+ 'auto_reconnect' => false,
+ 'min_backoff' => 1,
+ 'current_backoff' => 1,
+ 'max_backoff' => 32);
+
+ /**
+ * List of hosts we try to establish a connection to
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_host_list = array();
+
+ /**
+ * List of hosts that are known to be down.
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_down_host_list = array();
+
+ /**
+ * LDAP resource link.
+ *
+ * @access protected
+ * @var resource
+ */
+ protected $_link = false;
+
+ /**
+ * Net_LDAP2_Schema object
+ *
+ * This gets set and returned by {@link schema()}
+ *
+ * @access protected
+ * @var object Net_LDAP2_Schema
+ */
+ protected $_schema = null;
+
+ /**
+ * Schema cacher function callback
+ *
+ * @see registerSchemaCache()
+ * @var string
+ */
+ protected $_schema_cache = null;
+
+ /**
+ * Cache for attribute encoding checks
+ *
+ * @access protected
+ * @var array Hash with attribute names as key and boolean value
+ * to determine whether they should be utf8 encoded or not.
+ */
+ protected $_schemaAttrs = array();
+
+ /**
+ * Cache for rootDSE objects
+ *
+ * Hash with requested rootDSE attr names as key and rootDSE object as value
+ *
+ * Since the RootDSE object itself may request a rootDSE object,
+ * {@link rootDse()} caches successful requests.
+ * Internally, Net_LDAP2 needs several lookups to this object, so
+ * caching increases performance significally.
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_rootDSE_cache = array();
+
+ /**
+ * Returns the Net_LDAP2 Release version, may be called statically
+ *
+ * @static
+ * @return string Net_LDAP2 version
+ */
+ public static function getVersion()
+ {
+ return NET_LDAP2_VERSION;
+ }
+
+ /**
+ * Configure Net_LDAP2, connect and bind
+ *
+ * Use this method as starting point of using Net_LDAP2
+ * to establish a connection to your LDAP server.
+ *
+ * Static function that returns either an error object or the new Net_LDAP2
+ * object. Something like a factory. Takes a config array with the needed
+ * parameters.
+ *
+ * @param array $config Configuration array
+ *
+ * @access public
+ * @return Net_LDAP2_Error|Net_LDAP2 Net_LDAP2_Error or Net_LDAP2 object
+ */
+ public static function &connect($config = array())
+ {
+ $ldap_check = self::checkLDAPExtension();
+ if (self::iserror($ldap_check)) {
+ return $ldap_check;
+ }
+
+ @$obj = new Net_LDAP2($config);
+
+ // todo? better errorhandling for setConfig()?
+
+ // connect and bind with credentials in config
+ $err = $obj->bind();
+ if (self::isError($err)) {
+ return $err;
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Net_LDAP2 constructor
+ *
+ * Sets the config array
+ *
+ * Please note that the usual way of getting Net_LDAP2 to work is
+ * to call something like:
+ * <code>$ldap = Net_LDAP2::connect($ldap_config);</code>
+ *
+ * @param array $config Configuration array
+ *
+ * @access protected
+ * @return void
+ * @see $_config
+ */
+ public function __construct($config = array())
+ {
+ $this->PEAR('Net_LDAP2_Error');
+ $this->setConfig($config);
+ }
+
+ /**
+ * Sets the internal configuration array
+ *
+ * @param array $config Configuration array
+ *
+ * @access protected
+ * @return void
+ */
+ protected function setConfig($config)
+ {
+ //
+ // Parameter check -- probably should raise an error here if config
+ // is not an array.
+ //
+ if (! is_array($config)) {
+ return;
+ }
+
+ foreach ($config as $k => $v) {
+ if (isset($this->_config[$k])) {
+ $this->_config[$k] = $v;
+ } else {
+ // map old (Net_LDAP2) parms to new ones
+ switch($k) {
+ case "dn":
+ $this->_config["binddn"] = $v;
+ break;
+ case "password":
+ $this->_config["bindpw"] = $v;
+ break;
+ case "tls":
+ $this->_config["starttls"] = $v;
+ break;
+ case "base":
+ $this->_config["basedn"] = $v;
+ break;
+ }
+ }
+ }
+
+ //
+ // Ensure the host list is an array.
+ //
+ if (is_array($this->_config['host'])) {
+ $this->_host_list = $this->_config['host'];
+ } else {
+ if (strlen($this->_config['host']) > 0) {
+ $this->_host_list = array($this->_config['host']);
+ } else {
+ $this->_host_list = array();
+ // ^ this will cause an error in performConnect(),
+ // so the user is notified about the failure
+ }
+ }
+
+ //
+ // Reset the down host list, which seems like a sensible thing to do
+ // if the config is being reset for some reason.
+ //
+ $this->_down_host_list = array();
+ }
+
+ /**
+ * Bind or rebind to the ldap-server
+ *
+ * This function binds with the given dn and password to the server. In case
+ * no connection has been made yet, it will be started and startTLS issued
+ * if appropiate.
+ *
+ * The internal bind configuration is not being updated, so if you call
+ * bind() without parameters, you can rebind with the credentials
+ * provided at first connecting to the server.
+ *
+ * @param string $dn Distinguished name for binding
+ * @param string $password Password for binding
+ *
+ * @access public
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ public function bind($dn = null, $password = null)
+ {
+ // fetch current bind credentials
+ if (is_null($dn)) {
+ $dn = $this->_config["binddn"];
+ }
+ if (is_null($password)) {
+ $password = $this->_config["bindpw"];
+ }
+
+ // Connect first, if we haven't so far.
+ // This will also bind us to the server.
+ if ($this->_link === false) {
+ // store old credentials so we can revert them later
+ // then overwrite config with new bind credentials
+ $olddn = $this->_config["binddn"];
+ $oldpw = $this->_config["bindpw"];
+
+ // overwrite bind credentials in config
+ // so performConnect() knows about them
+ $this->_config["binddn"] = $dn;
+ $this->_config["bindpw"] = $password;
+
+ // try to connect with provided credentials
+ $msg = $this->performConnect();
+
+ // reset to previous config
+ $this->_config["binddn"] = $olddn;
+ $this->_config["bindpw"] = $oldpw;
+
+ // see if bind worked
+ if (self::isError($msg)) {
+ return $msg;
+ }
+ } else {
+ // do the requested bind as we are
+ // asked to bind manually
+ if (is_null($dn)) {
+ // anonymous bind
+ $msg = @ldap_bind($this->_link);
+ } else {
+ // privileged bind
+ $msg = @ldap_bind($this->_link, $dn, $password);
+ }
+ if (false === $msg) {
+ return PEAR::raiseError("Bind failed: " .
+ @ldap_error($this->_link),
+ @ldap_errno($this->_link));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Connect to the ldap-server
+ *
+ * This function connects to the LDAP server specified in
+ * the configuration, binds and set up the LDAP protocol as needed.
+ *
+ * @access protected
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ protected function performConnect()
+ {
+ // Note: Connecting is briefly described in RFC1777.
+ // Basicly it works like this:
+ // 1. set up TCP connection
+ // 2. secure that connection if neccessary
+ // 3a. setLDAPVersion to tell server which version we want to speak
+ // 3b. perform bind
+ // 3c. setLDAPVersion to tell server which version we want to speak
+ // together with a test for supported versions
+ // 4. set additional protocol options
+
+ // Return true if we are already connected.
+ if ($this->_link !== false) {
+ return true;
+ }
+
+ // Connnect to the LDAP server if we are not connected. Note that
+ // with some LDAP clients, ldapperformConnect returns a link value even
+ // if no connection is made. We need to do at least one anonymous
+ // bind to ensure that a connection is actually valid.
+ //
+ // Ref: http://www.php.net/manual/en/function.ldap-connect.php
+
+ // Default error message in case all connection attempts
+ // fail but no message is set
+ $current_error = new PEAR_Error('Unknown connection error');
+
+ // Catch empty $_host_list arrays.
+ if (!is_array($this->_host_list) || count($this->_host_list) == 0) {
+ $current_error = PEAR::raiseError('No Servers configured! Please '.
+ 'pass in an array of servers to Net_LDAP2');
+ return $current_error;
+ }
+
+ // Cycle through the host list.
+ foreach ($this->_host_list as $host) {
+
+ // Ensure we have a valid string for host name
+ if (is_array($host)) {
+ $current_error = PEAR::raiseError('No Servers configured! '.
+ 'Please pass in an one dimensional array of servers to '.
+ 'Net_LDAP2! (multidimensional array detected!)');
+ continue;
+ }
+
+ // Skip this host if it is known to be down.
+ if (in_array($host, $this->_down_host_list)) {
+ continue;
+ }
+
+ // Record the host that we are actually connecting to in case
+ // we need it later.
+ $this->_config['host'] = $host;
+
+ // Attempt a connection.
+ $this->_link = @ldap_connect($host, $this->_config['port']);
+ if (false === $this->_link) {
+ $current_error = PEAR::raiseError('Could not connect to ' .
+ $host . ':' . $this->_config['port']);
+ $this->_down_host_list[] = $host;
+ continue;
+ }
+
+ // If we're supposed to use TLS, do so before we try to bind,
+ // as some strict servers only allow binding via secure connections
+ if ($this->_config["starttls"] === true) {
+ if (self::isError($msg = $this->startTLS())) {
+ $current_error = $msg;
+ $this->_link = false;
+ $this->_down_host_list[] = $host;
+ continue;
+ }
+ }
+
+ // Try to set the configured LDAP version on the connection if LDAP
+ // server needs that before binding (eg OpenLDAP).
+ // This could be necessary since rfc-1777 states that the protocol version
+ // has to be set at the bind request.
+ // We use force here which means that the test in the rootDSE is skipped;
+ // this is neccessary, because some strict LDAP servers only allow to
+ // read the LDAP rootDSE (which tells us the supported protocol versions)
+ // with authenticated clients.
+ // This may fail in which case we try again after binding.
+ // In this case, most probably the bind() or setLDAPVersion()-call
+ // below will also fail, providing error messages.
+ $version_set = false;
+ $ignored_err = $this->setLDAPVersion(0, true);
+ if (!self::isError($ignored_err)) {
+ $version_set = true;
+ }
+
+ // Attempt to bind to the server. If we have credentials configured,
+ // we try to use them, otherwise its an anonymous bind.
+ // As stated by RFC-1777, the bind request should be the first
+ // operation to be performed after the connection is established.
+ // This may give an protocol error if the server does not support
+ // V2 binds and the above call to setLDAPVersion() failed.
+ // In case the above call failed, we try an V2 bind here and set the
+ // version afterwards (with checking to the rootDSE).
+ $msg = $this->bind();
+ if (self::isError($msg)) {
+ // The bind failed, discard link and save error msg.
+ // Then record the host as down and try next one
+ if ($msg->getCode() == 0x02 && !$version_set) {
+ // provide a finer grained error message
+ // if protocol error arieses because of invalid version
+ $msg = new Net_LDAP2_Error($msg->getMessage().
+ " (could not set LDAP protocol version to ".
+ $this->_config['version'].")",
+ $msg->getCode());
+ }
+ $this->_link = false;
+ $current_error = $msg;
+ $this->_down_host_list[] = $host;
+ continue;
+ }
+
+ // Set desired LDAP version if not successfully set before.
+ // Here, a check against the rootDSE is performed, so we get a
+ // error message if the server does not support the version.
+ // The rootDSE entry should tell us which LDAP versions are
+ // supported. However, some strict LDAP servers only allow
+ // bound suers to read the rootDSE.
+ if (!$version_set) {
+ if (self::isError($msg = $this->setLDAPVersion())) {
+ $current_error = $msg;
+ $this->_link = false;
+ $this->_down_host_list[] = $host;
+ continue;
+ }
+ }
+
+ // Set LDAP parameters, now we know we have a valid connection.
+ if (isset($this->_config['options']) &&
+ is_array($this->_config['options']) &&
+ count($this->_config['options'])) {
+ foreach ($this->_config['options'] as $opt => $val) {
+ $err = $this->setOption($opt, $val);
+ if (self::isError($err)) {
+ $current_error = $err;
+ $this->_link = false;
+ $this->_down_host_list[] = $host;
+ continue 2;
+ }
+ }
+ }
+
+ // At this stage we have connected, bound, and set up options,
+ // so we have a known good LDAP server. Time to go home.
+ return true;
+ }
+
+
+ // All connection attempts have failed, return the last error.
+ return $current_error;
+ }
+
+ /**
+ * Reconnect to the ldap-server.
+ *
+ * In case the connection to the LDAP
+ * service has dropped out for some reason, this function will reconnect,
+ * and re-bind if a bind has been attempted in the past. It is probably
+ * most useful when the server list provided to the new() or connect()
+ * function is an array rather than a single host name, because in that
+ * case it will be able to connect to a failover or secondary server in
+ * case the primary server goes down.
+ *
+ * This doesn't return anything, it just tries to re-establish
+ * the current connection. It will sleep for the current backoff
+ * period (seconds) before attempting the connect, and if the
+ * connection fails it will double the backoff period, but not
+ * try again. If you want to ensure a reconnection during a
+ * transient period of server downtime then you need to call this
+ * function in a loop.
+ *
+ * @access protected
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ protected function performReconnect()
+ {
+
+ // Return true if we are already connected.
+ if ($this->_link !== false) {
+ return true;
+ }
+
+ // Default error message in case all connection attempts
+ // fail but no message is set
+ $current_error = new PEAR_Error('Unknown connection error');
+
+ // Sleep for a backoff period in seconds.
+ sleep($this->_config['current_backoff']);
+
+ // Retry all available connections.
+ $this->_down_host_list = array();
+ $msg = $this->performConnect();
+
+ // Bail out if that fails.
+ if (self::isError($msg)) {
+ $this->_config['current_backoff'] =
+ $this->_config['current_backoff'] * 2;
+ if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
+ $this->_config['current_backoff'] = $this->_config['max_backoff'];
+ }
+ return $msg;
+ }
+
+ // Now we should be able to safely (re-)bind.
+ $msg = $this->bind();
+ if (self::isError($msg)) {
+ $this->_config['current_backoff'] = $this->_config['current_backoff'] * 2;
+ if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
+ $this->_config['current_backoff'] = $this->_config['max_backoff'];
+ }
+
+ // _config['host'] should have had the last connected host stored in it
+ // by performConnect(). Since we are unable to bind to that host we can safely
+ // assume that it is down or has some other problem.
+ $this->_down_host_list[] = $this->_config['host'];
+ return $msg;
+ }
+
+ // At this stage we have connected, bound, and set up options,
+ // so we have a known good LDAP server. Time to go home.
+ $this->_config['current_backoff'] = $this->_config['min_backoff'];
+ return true;
+ }
+
+ /**
+ * Starts an encrypted session
+ *
+ * @access public
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ public function startTLS()
+ {
+ // Test to see if the server supports TLS first.
+ // This is done via testing the extensions offered by the server.
+ // The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
+ $rootDSE = $this->rootDse();
+ if (self::isError($rootDSE)) {
+ return $this->raiseError("Unable to fetch rootDSE entry ".
+ "to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
+ }
+
+ $supported_extensions = $rootDSE->getValue('supportedExtension');
+ if (self::isError($supported_extensions)) {
+ return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
+ "to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
+ }
+
+ if (in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
+ if (false === @ldap_start_tls($this->_link)) {
+ return $this->raiseError("TLS not started: " .
+ @ldap_error($this->_link),
+ @ldap_errno($this->_link));
+ }
+ return true;
+ } else {
+ return $this->raiseError("Server reports that it does not support TLS");
+ }
+ }
+
+ /**
+ * alias function of startTLS() for perl-ldap interface
+ *
+ * @return void
+ * @see startTLS()
+ */
+ public function start_tls()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'startTLS' ), $args);
+ }
+
+ /**
+ * Close LDAP connection.
+ *
+ * Closes the connection. Use this when the session is over.
+ *
+ * @return void
+ */
+ public function done()
+ {
+ $this->_Net_LDAP2();
+ }
+
+ /**
+ * Alias for {@link done()}
+ *
+ * @return void
+ * @see done()
+ */
+ public function disconnect()
+ {
+ $this->done();
+ }
+
+ /**
+ * Destructor
+ *
+ * @access protected
+ */
+ public function _Net_LDAP2()
+ {
+ @ldap_close($this->_link);
+ }
+
+ /**
+ * Add a new entryobject to a directory.
+ *
+ * Use add to add a new Net_LDAP2_Entry object to the directory.
+ * This also links the entry to the connection used for the add,
+ * if it was a fresh entry ({@link Net_LDAP2_Entry::createFresh()})
+ *
+ * @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry
+ *
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ public function add(&$entry)
+ {
+ if (!$entry instanceof Net_LDAP2_Entry) {
+ return PEAR::raiseError('Parameter to Net_LDAP2::add() must be a Net_LDAP2_Entry object.');
+ }
+
+ // Continue attempting the add operation in a loop until we
+ // get a success, a definitive failure, or the world ends.
+ $foo = 0;
+ while (true) {
+ $link = $this->getLink();
+
+ if ($link === false) {
+ // We do not have a successful connection yet. The call to
+ // getLink() would have kept trying if we wanted one. Go
+ // home now.
+ return PEAR::raiseError("Could not add entry " . $entry->dn() .
+ " no valid LDAP connection could be found.");
+ }
+
+ if (@ldap_add($link, $entry->dn(), $entry->getValues())) {
+ // entry successfully added, we should update its $ldap reference
+ // in case it is not set so far (fresh entry)
+ if (!$entry->getLDAP() instanceof Net_LDAP2) {
+ $entry->setLDAP($this);
+ }
+ // store, that the entry is present inside the directory
+ $entry->markAsNew(false);
+ return true;
+ } else {
+ // We have a failure. What type? We may be able to reconnect
+ // and try again.
+ $error_code = @ldap_errno($link);
+ $error_name = $this->errorMessage($error_code);
+
+ if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
+ ($this->_config['auto_reconnect'])) {
+
+ // The server has become disconnected before trying the
+ // operation. We should try again, possibly with a different
+ // server.
+ $this->_link = false;
+ $this->performReconnect();
+ } else {
+ // Errors other than the above catched are just passed
+ // back to the user so he may react upon them.
+ return PEAR::raiseError("Could not add entry " . $entry->dn() . " " .
+ $error_name,
+ $error_code);
+ }
+ }
+ }
+ }
+
+ /**
+ * Delete an entry from the directory
+ *
+ * The object may either be a string representing the dn or a Net_LDAP2_Entry
+ * object. When the boolean paramter recursive is set, all subentries of the
+ * entry will be deleted as well.
+ *
+ * @param string|Net_LDAP2_Entry $dn DN-string or Net_LDAP2_Entry
+ * @param boolean $recursive Should we delete all children recursive as well?
+ *
+ * @access public
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ public function delete($dn, $recursive = false)
+ {
+ if ($dn instanceof Net_LDAP2_Entry) {
+ $dn = $dn->dn();
+ }
+ if (false === is_string($dn)) {
+ return PEAR::raiseError("Parameter is not a string nor an entry object!");
+ }
+ // Recursive delete searches for children and calls delete for them
+ if ($recursive) {
+ $result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0);
+ if (@ldap_count_entries($this->_link, $result)) {
+ $subentry = @ldap_first_entry($this->_link, $result);
+ $this->delete(@ldap_get_dn($this->_link, $subentry), true);
+ while ($subentry = @ldap_next_entry($this->_link, $subentry)) {
+ $this->delete(@ldap_get_dn($this->_link, $subentry), true);
+ }
+ }
+ }
+
+ // Continue attempting the delete operation in a loop until we
+ // get a success, a definitive failure, or the world ends.
+ while (true) {
+ $link = $this->getLink();
+
+ if ($link === false) {
+ // We do not have a successful connection yet. The call to
+ // getLink() would have kept trying if we wanted one. Go
+ // home now.
+ return PEAR::raiseError("Could not add entry " . $dn .
+ " no valid LDAP connection could be found.");
+ }
+
+ if (@ldap_delete($link, $dn)) {
+ // entry successfully deleted.
+ return true;
+ } else {
+ // We have a failure. What type?
+ // We may be able to reconnect and try again.
+ $error_code = @ldap_errno($link);
+ $error_name = $this->errorMessage($error_code);
+
+ if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
+ ($this->_config['auto_reconnect'])) {
+ // The server has become disconnected before trying the
+ // operation. We should try again, possibly with a
+ // different server.
+ $this->_link = false;
+ $this->performReconnect();
+
+ } elseif ($error_code == 66) {
+ // Subentries present, server refused to delete.
+ // Deleting subentries is the clients responsibility, but
+ // since the user may not know of the subentries, we do not
+ // force that here but instead notify the developer so he
+ // may take actions himself.
+ return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive parameter to delete them.");
+
+ } else {
+ // Errors other than the above catched are just passed
+ // back to the user so he may react upon them.
+ return PEAR::raiseError("Could not delete entry " . $dn . " " .
+ $error_name,
+ $error_code);
+ }
+ }
+ }
+ }
+
+ /**
+ * Modify an ldapentry directly on the server
+ *
+ * This one takes the DN or a Net_LDAP2_Entry object and an array of actions.
+ * This array should be something like this:
+ *
+ * array('add' => array('attribute1' => array('val1', 'val2'),
+ * 'attribute2' => array('val1')),
+ * 'delete' => array('attribute1'),
+ * 'replace' => array('attribute1' => array('val1')),
+ * 'changes' => array('add' => ...,
+ * 'replace' => ...,
+ * 'delete' => array('attribute1', 'attribute2' => array('val1')))
+ *
+ * The changes array is there so the order of operations can be influenced
+ * (the operations are done in order of appearance).
+ * The order of execution is as following:
+ * 1. adds from 'add' array
+ * 2. deletes from 'delete' array
+ * 3. replaces from 'replace' array
+ * 4. changes (add, replace, delete) in order of appearance
+ * All subarrays (add, replace, delete, changes) may be given at the same time.
+ *
+ * The function calls the corresponding functions of an Net_LDAP2_Entry
+ * object. A detailed description of array structures can be found there.
+ *
+ * Unlike the modification methods provided by the Net_LDAP2_Entry object,
+ * this method will instantly carry out an update() after each operation,
+ * thus modifying "directly" on the server.
+ *
+ * @param string|Net_LDAP2_Entry $entry DN-string or Net_LDAP2_Entry
+ * @param array $parms Array of changes
+ *
+ * @access public
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ public function modify($entry, $parms = array())
+ {
+ if (is_string($entry)) {
+ $entry = $this->getEntry($entry);
+ if (self::isError($entry)) {
+ return $entry;
+ }
+ }
+ if (!$entry instanceof Net_LDAP2_Entry) {
+ return PEAR::raiseError("Parameter is not a string nor an entry object!");
+ }
+
+ // Perform changes mentioned separately
+ foreach (array('add', 'delete', 'replace') as $action) {
+ if (isset($parms[$action])) {
+ $msg = $entry->$action($parms[$action]);
+ if (self::isError($msg)) {
+ return $msg;
+ }
+ $entry->setLDAP($this);
+
+ // Because the @ldap functions are called inside Net_LDAP2_Entry::update(),
+ // we have to trap the error codes issued from that if we want to support
+ // reconnection.
+ while (true) {
+ $msg = $entry->update();
+
+ if (self::isError($msg)) {
+ // We have a failure. What type? We may be able to reconnect
+ // and try again.
+ $error_code = $msg->getCode();
+ $error_name = $this->errorMessage($error_code);
+
+ if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
+ ($this->_config['auto_reconnect'])) {
+
+ // The server has become disconnected before trying the
+ // operation. We should try again, possibly with a different
+ // server.
+ $this->_link = false;
+ $this->performReconnect();
+
+ } else {
+
+ // Errors other than the above catched are just passed
+ // back to the user so he may react upon them.
+ return PEAR::raiseError("Could not modify entry: ".$msg->getMessage());
+ }
+ } else {
+ // modification succeedet, evaluate next change
+ break;
+ }
+ }
+ }
+ }
+
+ // perform combined changes in 'changes' array
+ if (isset($parms['changes']) && is_array($parms['changes'])) {
+ foreach ($parms['changes'] as $action => $value) {
+
+ // Because the @ldap functions are called inside Net_LDAP2_Entry::update,
+ // we have to trap the error codes issued from that if we want to support
+ // reconnection.
+ while (true) {
+ $msg = $this->modify($entry, array($action => $value));
+
+ if (self::isError($msg)) {
+ // We have a failure. What type? We may be able to reconnect
+ // and try again.
+ $error_code = $msg->getCode();
+ $error_name = $this->errorMessage($error_code);
+
+ if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
+ ($this->_config['auto_reconnect'])) {
+
+ // The server has become disconnected before trying the
+ // operation. We should try again, possibly with a different
+ // server.
+ $this->_link = false;
+ $this->performReconnect();
+
+ } else {
+ // Errors other than the above catched are just passed
+ // back to the user so he may react upon them.
+ return $msg;
+ }
+ } else {
+ // modification succeedet, evaluate next change
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run a ldap search query
+ *
+ * Search is used to query the ldap-database.
+ * $base and $filter may be ommitted. The one from config will
+ * then be used. $base is either a DN-string or an Net_LDAP2_Entry
+ * object in which case its DN willb e used.
+ *
+ * Params may contain:
+ *
+ * scope: The scope which will be used for searching
+ * base - Just one entry
+ * sub - The whole tree
+ * one - Immediately below $base
+ * sizelimit: Limit the number of entries returned (default: 0 = unlimited),
+ * timelimit: Limit the time spent for searching (default: 0 = unlimited),
+ * attrsonly: If true, the search will only return the attribute names,
+ * attributes: Array of attribute names, which the entry should contain.
+ * It is good practice to limit this to just the ones you need.
+ * [NOT IMPLEMENTED]
+ * deref: By default aliases are dereferenced to locate the base object for the search, but not when
+ * searching subordinates of the base object. This may be changed by specifying one of the
+ * following values:
+ *
+ * never - Do not dereference aliases in searching or in locating the base object of the search.
+ * search - Dereference aliases in subordinates of the base object in searching, but not in
+ * locating the base object of the search.
+ * find
+ * always
+ *
+ * Please note, that you cannot override server side limitations to sizelimit
+ * and timelimit: You can always only lower a given limit.
+ *
+ * @param string|Net_LDAP2_Entry $base LDAP searchbase
+ * @param string|Net_LDAP2_Filter $filter LDAP search filter or a Net_LDAP2_Filter object
+ * @param array $params Array of options
+ *
+ * @access public
+ * @return Net_LDAP2_Search|Net_LDAP2_Error Net_LDAP2_Search object or Net_LDAP2_Error object
+ * @todo implement search controls (sorting etc)
+ */
+ public function search($base = null, $filter = null, $params = array())
+ {
+ if (is_null($base)) {
+ $base = $this->_config['basedn'];
+ }
+ if ($base instanceof Net_LDAP2_Entry) {
+ $base = $base->dn(); // fetch DN of entry, making searchbase relative to the entry
+ }
+ if (is_null($filter)) {
+ $filter = $this->_config['filter'];
+ }
+ if ($filter instanceof Net_LDAP2_Filter) {
+ $filter = $filter->asString(); // convert Net_LDAP2_Filter to string representation
+ }
+ if (PEAR::isError($filter)) {
+ return $filter;
+ }
+ if (PEAR::isError($base)) {
+ return $base;
+ }
+
+ /* setting searchparameters */
+ (isset($params['sizelimit'])) ? $sizelimit = $params['sizelimit'] : $sizelimit = 0;
+ (isset($params['timelimit'])) ? $timelimit = $params['timelimit'] : $timelimit = 0;
+ (isset($params['attrsonly'])) ? $attrsonly = $params['attrsonly'] : $attrsonly = 0;
+ (isset($params['attributes'])) ? $attributes = $params['attributes'] : $attributes = array();
+
+ // Ensure $attributes to be an array in case only one
+ // attribute name was given as string
+ if (!is_array($attributes)) {
+ $attributes = array($attributes);
+ }
+
+ // reorganize the $attributes array index keys
+ // sometimes there are problems with not consecutive indexes
+ $attributes = array_values($attributes);
+
+ // scoping makes searches faster!
+ $scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']);
+
+ switch ($scope) {
+ case 'one':
+ $search_function = 'ldap_list';
+ break;
+ case 'base':
+ $search_function = 'ldap_read';
+ break;
+ default:
+ $search_function = 'ldap_search';
+ }
+
+ // Continue attempting the search operation until we get a success
+ // or a definitive failure.
+ while (true) {
+ $link = $this->getLink();
+ $search = @call_user_func($search_function,
+ $link,
+ $base,
+ $filter,
+ $attributes,
+ $attrsonly,
+ $sizelimit,
+ $timelimit);
+
+ if ($err = @ldap_errno($link)) {
+ if ($err == 32) {
+ // Errorcode 32 = no such object, i.e. a nullresult.
+ return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
+ } elseif ($err == 4) {
+ // Errorcode 4 = sizelimit exeeded.
+ return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
+ } elseif ($err == 87) {
+ // bad search filter
+ return $this->raiseError($this->errorMessage($err) . "($filter)", $err);
+ } elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
+ // Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
+ $this->_link = false;
+ $this->performReconnect();
+ } else {
+ $msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
+ return $this->raiseError($this->errorMessage($err) . $msg, $err);
+ }
+ } else {
+ return $obj = new Net_LDAP2_Search($search, $this, $attributes);
+ }
+ }
+ }
+
+ /**
+ * Set an LDAP option
+ *
+ * @param string $option Option to set
+ * @param mixed $value Value to set Option to
+ *
+ * @access public
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ */
+ public function setOption($option, $value)
+ {
+ if ($this->_link) {
+ if (defined($option)) {
+ if (@ldap_set_option($this->_link, constant($option), $value)) {
+ return true;
+ } else {
+ $err = @ldap_errno($this->_link);
+ if ($err) {
+ $msg = @ldap_err2str($err);
+ } else {
+ $err = NET_LDAP2_ERROR;
+ $msg = $this->errorMessage($err);
+ }
+ return $this->raiseError($msg, $err);
+ }
+ } else {
+ return $this->raiseError("Unkown Option requested");
+ }
+ } else {
+ return $this->raiseError("Could not set LDAP option: No LDAP connection");
+ }
+ }
+
+ /**
+ * Get an LDAP option value
+ *
+ * @param string $option Option to get
+ *
+ * @access public
+ * @return Net_LDAP2_Error|string Net_LDAP2_Error or option value
+ */
+ public function getOption($option)
+ {
+ if ($this->_link) {
+ if (defined($option)) {
+ if (@ldap_get_option($this->_link, constant($option), $value)) {
+ return $value;
+ } else {
+ $err = @ldap_errno($this->_link);
+ if ($err) {
+ $msg = @ldap_err2str($err);
+ } else {
+ $err = NET_LDAP2_ERROR;
+ $msg = $this->errorMessage($err);
+ }
+ return $this->raiseError($msg, $err);
+ }
+ } else {
+ $this->raiseError("Unkown Option requested");
+ }
+ } else {
+ $this->raiseError("No LDAP connection");
+ }
+ }
+
+ /**
+ * Get the LDAP_PROTOCOL_VERSION that is used on the connection.
+ *
+ * A lot of ldap functionality is defined by what protocol version the ldap server speaks.
+ * This might be 2 or 3.
+ *
+ * @return int
+ */
+ public function getLDAPVersion()
+ {
+ if ($this->_link) {
+ $version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION");
+ } else {
+ $version = $this->_config['version'];
+ }
+ return $version;
+ }
+
+ /**
+ * Set the LDAP_PROTOCOL_VERSION that is used on the connection.
+ *
+ * @param int $version LDAP-version that should be used
+ * @param boolean $force If set to true, the check against the rootDSE will be skipped
+ *
+ * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
+ * @todo Checking via the rootDSE takes much time - why? fetching and instanciation is quick!
+ */
+ public function setLDAPVersion($version = 0, $force = false)
+ {
+ if (!$version) {
+ $version = $this->_config['version'];
+ }
+
+ //
+ // Check to see if the server supports this version first.
+ //
+ // Todo: Why is this so horribly slow?
+ // $this->rootDse() is very fast, as well as Net_LDAP2_RootDSE::fetch()
+ // seems like a problem at copiyng the object inside PHP??
+ // Additionally, this is not always reproducable...
+ //
+ if (!$force) {
+ $rootDSE = $this->rootDse();
+ if ($rootDSE instanceof Net_LDAP2_Error) {
+ return $rootDSE;
+ } else {
+ $supported_versions = $rootDSE->getValue('supportedLDAPVersion');
+ if (is_string($supported_versions)) {
+ $supported_versions = array($supported_versions);
+ }
+ $check_ok = in_array($version, $supported_versions);
+ }
+ }
+
+ if ($force || $check_ok) {
+ return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version);
+ } else {
+ return $this->raiseError("LDAP Server does not support protocol version " . $version);
+ }
+ }
+
+
+ /**
+ * Tells if a DN does exist in the directory
+ *
+ * @param string|Net_LDAP2_Entry $dn The DN of the object to test
+ *
+ * @return boolean|Net_LDAP2_Error
+ */
+ public function dnExists($dn)
+ {
+ if (PEAR::isError($dn)) {
+ return $dn;
+ }
+ if ($dn instanceof Net_LDAP2_Entry) {
+ $dn = $dn->dn();
+ }
+ if (false === is_string($dn)) {
+ return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
+ }
+
+ // make dn relative to parent
+ $base = Net_LDAP2_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false));
+ if (self::isError($base)) {
+ return $base;
+ }
+ $entry_rdn = array_shift($base);
+ if (is_array($entry_rdn)) {
+ // maybe the dn consist of a multivalued RDN, we must build the dn in this case
+ // because the $entry_rdn is an array!
+ $filter_dn = Net_LDAP2_Util::canonical_dn($entry_rdn);
+ }
+ $base = Net_LDAP2_Util::canonical_dn($base);
+
+ $result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1);
+ if (@ldap_count_entries($this->_link, $result)) {
+ return true;
+ }
+ if (ldap_errno($this->_link) == 32) {
+ return false;
+ }
+ if (ldap_errno($this->_link) != 0) {
+ return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link));
+ }
+ return false;
+ }
+
+
+ /**
+ * Get a specific entry based on the DN
+ *
+ * @param string $dn DN of the entry that should be fetched
+ * @param array $attr Array of Attributes to select. If ommitted, all attributes are fetched.
+ *
+ * @return Net_LDAP2_Entry|Net_LDAP2_Error Reference to a Net_LDAP2_Entry object or Net_LDAP2_Error object
+ * @todo Maybe check against the shema should be done to be sure the attribute type exists
+ */
+ public function &getEntry($dn, $attr = array())
+ {
+ if (!is_array($attr)) {
+ $attr = array($attr);
+ }
+ $result = $this->search($dn, '(objectClass=*)',
+ array('scope' => 'base', 'attributes' => $attr));
+ if (self::isError($result)) {
+ return $result;
+ } elseif ($result->count() == 0) {
+ return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found');
+ }
+ $entry = $result->shiftEntry();
+ if (false == $entry) {
+ return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)');
+ }
+ return $entry;
+ }
+
+ /**
+ * Rename or move an entry
+ *
+ * This method will instantly carry out an update() after the move,
+ * so the entry is moved instantly.
+ * You can pass an optional Net_LDAP2 object. In this case, a cross directory
+ * move will be performed which deletes the entry in the source (THIS) directory
+ * and adds it in the directory $target_ldap.
+ * A cross directory move will switch the Entrys internal LDAP reference so
+ * updates to the entry will go to the new directory.
+ *
+ * Note that if you want to do a cross directory move, you need to
+ * pass an Net_LDAP2_Entry object, otherwise the attributes will be empty.
+ *
+ * @param string|Net_LDAP2_Entry $entry Entry DN or Entry object
+ * @param string $newdn New location
+ * @param Net_LDAP2 $target_ldap (optional) Target directory for cross server move; should be passed via reference
+ *
+ * @return Net_LDAP2_Error|true
+ */
+ public function move($entry, $newdn, $target_ldap = null)
+ {
+ if (is_string($entry)) {
+ $entry_o = $this->getEntry($entry);
+ } else {
+ $entry_o =& $entry;
+ }
+ if (!$entry_o instanceof Net_LDAP2_Entry) {
+ return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object! (If DN was passed, conversion failed)');
+ }
+ if (null !== $target_ldap && !$target_ldap instanceof Net_LDAP2) {
+ return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP2 object!');
+ }
+
+ if ($target_ldap && $target_ldap !== $this) {
+ // cross directory move
+ if (is_string($entry)) {
+ return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP2_Entry object');
+ }
+ if ($target_ldap->dnExists($newdn)) {
+ return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory');
+ }
+ $entry_o->dn($newdn);
+ $res = $target_ldap->add($entry_o);
+ if (self::isError($res)) {
+ return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory');
+ }
+ $res = $this->delete($entry_o->currentDN());
+ if (self::isError($res)) {
+ $res2 = $target_ldap->delete($entry_o); // undo add
+ if (self::isError($res2)) {
+ $add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.';
+ }
+ return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string);
+ }
+ $entry_o->setLDAP($target_ldap);
+ return true;
+ } else {
+ // local move
+ $entry_o->dn($newdn);
+ $entry_o->setLDAP($this);
+ return $entry_o->update();
+ }
+ }
+
+ /**
+ * Copy an entry to a new location
+ *
+ * The entry will be immediately copied.
+ * Please note that only attributes you have
+ * selected will be copied.
+ *
+ * @param Net_LDAP2_Entry &$entry Entry object
+ * @param string $newdn New FQF-DN of the entry
+ *
+ * @return Net_LDAP2_Error|Net_LDAP2_Entry Error Message or reference to the copied entry
+ */
+ public function &copy(&$entry, $newdn)
+ {
+ if (!$entry instanceof Net_LDAP2_Entry) {
+ return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object!');
+ }
+
+ $newentry = Net_LDAP2_Entry::createFresh($newdn, $entry->getValues());
+ $result = $this->add($newentry);
+
+ if ($result instanceof Net_LDAP2_Error) {
+ return $result;
+ } else {
+ return $newentry;
+ }
+ }
+
+
+ /**
+ * Returns the string for an ldap errorcode.
+ *
+ * Made to be able to make better errorhandling
+ * Function based on DB::errorMessage()
+ * Tip: The best description of the errorcodes is found here:
+ * http://www.directory-info.com/LDAP2/LDAPErrorCodes.html
+ *
+ * @param int $errorcode Error code
+ *
+ * @return string The errorstring for the error.
+ */
+ public function errorMessage($errorcode)
+ {
+ $errorMessages = array(
+ 0x00 => "LDAP_SUCCESS",
+ 0x01 => "LDAP_OPERATIONS_ERROR",
+ 0x02 => "LDAP_PROTOCOL_ERROR",
+ 0x03 => "LDAP_TIMELIMIT_EXCEEDED",
+ 0x04 => "LDAP_SIZELIMIT_EXCEEDED",
+ 0x05 => "LDAP_COMPARE_FALSE",
+ 0x06 => "LDAP_COMPARE_TRUE",
+ 0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED",
+ 0x08 => "LDAP_STRONG_AUTH_REQUIRED",
+ 0x09 => "LDAP_PARTIAL_RESULTS",
+ 0x0a => "LDAP_REFERRAL",
+ 0x0b => "LDAP_ADMINLIMIT_EXCEEDED",
+ 0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION",
+ 0x0d => "LDAP_CONFIDENTIALITY_REQUIRED",
+ 0x0e => "LDAP_SASL_BIND_INPROGRESS",
+ 0x10 => "LDAP_NO_SUCH_ATTRIBUTE",
+ 0x11 => "LDAP_UNDEFINED_TYPE",
+ 0x12 => "LDAP_INAPPROPRIATE_MATCHING",
+ 0x13 => "LDAP_CONSTRAINT_VIOLATION",
+ 0x14 => "LDAP_TYPE_OR_VALUE_EXISTS",
+ 0x15 => "LDAP_INVALID_SYNTAX",
+ 0x20 => "LDAP_NO_SUCH_OBJECT",
+ 0x21 => "LDAP_ALIAS_PROBLEM",
+ 0x22 => "LDAP_INVALID_DN_SYNTAX",
+ 0x23 => "LDAP_IS_LEAF",
+ 0x24 => "LDAP_ALIAS_DEREF_PROBLEM",
+ 0x30 => "LDAP_INAPPROPRIATE_AUTH",
+ 0x31 => "LDAP_INVALID_CREDENTIALS",
+ 0x32 => "LDAP_INSUFFICIENT_ACCESS",
+ 0x33 => "LDAP_BUSY",
+ 0x34 => "LDAP_UNAVAILABLE",
+ 0x35 => "LDAP_UNWILLING_TO_PERFORM",
+ 0x36 => "LDAP_LOOP_DETECT",
+ 0x3C => "LDAP_SORT_CONTROL_MISSING",
+ 0x3D => "LDAP_INDEX_RANGE_ERROR",
+ 0x40 => "LDAP_NAMING_VIOLATION",
+ 0x41 => "LDAP_OBJECT_CLASS_VIOLATION",
+ 0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF",
+ 0x43 => "LDAP_NOT_ALLOWED_ON_RDN",
+ 0x44 => "LDAP_ALREADY_EXISTS",
+ 0x45 => "LDAP_NO_OBJECT_CLASS_MODS",
+ 0x46 => "LDAP_RESULTS_TOO_LARGE",
+ 0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS",
+ 0x50 => "LDAP_OTHER",
+ 0x51 => "LDAP_SERVER_DOWN",
+ 0x52 => "LDAP_LOCAL_ERROR",
+ 0x53 => "LDAP_ENCODING_ERROR",
+ 0x54 => "LDAP_DECODING_ERROR",
+ 0x55 => "LDAP_TIMEOUT",
+ 0x56 => "LDAP_AUTH_UNKNOWN",
+ 0x57 => "LDAP_FILTER_ERROR",
+ 0x58 => "LDAP_USER_CANCELLED",
+ 0x59 => "LDAP_PARAM_ERROR",
+ 0x5a => "LDAP_NO_MEMORY",
+ 0x5b => "LDAP_CONNECT_ERROR",
+ 0x5c => "LDAP_NOT_SUPPORTED",
+ 0x5d => "LDAP_CONTROL_NOT_FOUND",
+ 0x5e => "LDAP_NO_RESULTS_RETURNED",
+ 0x5f => "LDAP_MORE_RESULTS_TO_RETURN",
+ 0x60 => "LDAP_CLIENT_LOOP",
+ 0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED",
+ 1000 => "Unknown Net_LDAP2 Error"
+ );
+
+ return isset($errorMessages[$errorcode]) ?
+ $errorMessages[$errorcode] :
+ $errorMessages[NET_LDAP2_ERROR] . ' (' . $errorcode . ')';
+ }
+
+ /**
+ * Gets a rootDSE object
+ *
+ * This either fetches a fresh rootDSE object or returns it from
+ * the internal cache for performance reasons, if possible.
+ *
+ * @param array $attrs Array of attributes to search for
+ *
+ * @access public
+ * @return Net_LDAP2_Error|Net_LDAP2_RootDSE Net_LDAP2_Error or Net_LDAP2_RootDSE object
+ */
+ public function &rootDse($attrs = null)
+ {
+ if ($attrs !== null && !is_array($attrs)) {
+ return PEAR::raiseError('Parameter $attr is expected to be an array!');
+ }
+
+ $attrs_signature = serialize($attrs);
+
+ // see if we need to fetch a fresh object, or if we already
+ // requested this object with the same attributes
+ if (true || !array_key_exists($attrs_signature, $this->_rootDSE_cache)) {
+ $rootdse =& Net_LDAP2_RootDSE::fetch($this, $attrs);
+ if ($rootdse instanceof Net_LDAP2_Error) {
+ return $rootdse;
+ }
+
+ // search was ok, store rootDSE in cache
+ $this->_rootDSE_cache[$attrs_signature] = $rootdse;
+ }
+ return $this->_rootDSE_cache[$attrs_signature];
+ }
+
+ /**
+ * Alias function of rootDse() for perl-ldap interface
+ *
+ * @access public
+ * @see rootDse()
+ * @return Net_LDAP2_Error|Net_LDAP2_RootDSE
+ */
+ public function &root_dse()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array(&$this, 'rootDse'), $args);
+ }
+
+ /**
+ * Get a schema object
+ *
+ * @param string $dn (optional) Subschema entry dn
+ *
+ * @access public
+ * @return Net_LDAP2_Schema|Net_LDAP2_Error Net_LDAP2_Schema or Net_LDAP2_Error object
+ */
+ public function &schema($dn = null)
+ {
+ // Schema caching by Knut-Olav Hoven
+ // If a schema caching object is registered, we use that to fetch
+ // a schema object.
+ // See registerSchemaCache() for more info on this.
+ if ($this->_schema === null) {
+ if ($this->_schema_cache) {
+ $cached_schema = $this->_schema_cache->loadSchema();
+ if ($cached_schema instanceof Net_LDAP2_Error) {
+ return $cached_schema; // route error to client
+ } else {
+ if ($cached_schema instanceof Net_LDAP2_Schema) {
+ $this->_schema = $cached_schema;
+ }
+ }
+ }
+ }
+
+ // Fetch schema, if not tried before and no cached version available.
+ // If we are already fetching the schema, we will skip fetching.
+ if ($this->_schema === null) {
+ // store a temporary error message so subsequent calls to schema() can
+ // detect, that we are fetching the schema already.
+ // Otherwise we will get an infinite loop at Net_LDAP2_Schema::fetch()
+ $this->_schema = new Net_LDAP2_Error('Schema not initialized');
+ $this->_schema = Net_LDAP2_Schema::fetch($this, $dn);
+
+ // If schema caching is active, advise the cache to store the schema
+ if ($this->_schema_cache) {
+ $caching_result = $this->_schema_cache->storeSchema($this->_schema);
+ if ($caching_result instanceof Net_LDAP2_Error) {
+ return $caching_result; // route error to client
+ }
+ }
+ }
+ return $this->_schema;
+ }
+
+ /**
+ * Enable/disable persistent schema caching
+ *
+ * Sometimes it might be useful to allow your scripts to cache
+ * the schema information on disk, so the schema is not fetched
+ * every time the script runs which could make your scripts run
+ * faster.
+ *
+ * This method allows you to register a custom object that
+ * implements your schema cache. Please see the SchemaCache interface
+ * (SchemaCache.interface.php) for informations on how to implement this.
+ * To unregister the cache, pass null as $cache parameter.
+ *
+ * For ease of use, Net_LDAP2 provides a simple file based cache
+ * which is used in the example below. You may use this, for example,
+ * to store the schema in a linux tmpfs which results in the schema
+ * beeing cached inside the RAM which allows nearly instant access.
+ * <code>
+ * // Create the simple file cache object that comes along with Net_LDAP2
+ * $mySchemaCache_cfg = array(
+ * 'path' => '/tmp/Net_LDAP2_Schema.cache',
+ * 'max_age' => 86400 // max age is 24 hours (in seconds)
+ * );
+ * $mySchemaCache = new Net_LDAP2_SimpleFileSchemaCache($mySchemaCache_cfg);
+ * $ldap = new Net_LDAP2::connect(...);
+ * $ldap->registerSchemaCache($mySchemaCache); // enable caching
+ * // now each call to $ldap->schema() will get the schema from disk!
+ * </code>
+ *
+ * @param Net_LDAP2_SchemaCache|null $cache Object implementing the Net_LDAP2_SchemaCache interface
+ *
+ * @return true|Net_LDAP2_Error
+ */
+ public function registerSchemaCache($cache) {
+ if (is_null($cache)
+ || (is_object($cache) && in_array('Net_LDAP2_SchemaCache', class_implements($cache))) ) {
+ $this->_schema_cache = $cache;
+ return true;
+ } else {
+ return new Net_LDAP2_Error('Custom schema caching object is either no '.
+ 'valid object or does not implement the Net_LDAP2_SchemaCache interface!');
+ }
+ }
+
+
+ /**
+ * Checks if phps ldap-extension is loaded
+ *
+ * If it is not loaded, it tries to load it manually using PHPs dl().
+ * It knows both windows-dll and *nix-so.
+ *
+ * @static
+ * @return Net_LDAP2_Error|true
+ */
+ public static function checkLDAPExtension()
+ {
+ if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
+ return new Net_LDAP2_Error("It seems that you do not have the ldap-extension installed. Please install it before using the Net_LDAP2 package.");
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Encodes given attributes to UTF8 if needed by schema
+ *
+ * This function takes attributes in an array and then checks against the schema if they need
+ * UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
+ * can be used for adding or modifying.
+ *
+ * $attributes is expected to be an array with keys describing
+ * the attribute names and the values as the value of this attribute:
+ * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
+ *
+ * @param array $attributes Array of attributes
+ *
+ * @access public
+ * @return array|Net_LDAP2_Error Array of UTF8 encoded attributes or Error
+ */
+ public function utf8Encode($attributes)
+ {
+ return $this->utf8($attributes, 'utf8_encode');
+ }
+
+ /**
+ * Decodes the given attribute values if needed by schema
+ *
+ * $attributes is expected to be an array with keys describing
+ * the attribute names and the values as the value of this attribute:
+ * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
+ *
+ * @param array $attributes Array of attributes
+ *
+ * @access public
+ * @see utf8Encode()
+ * @return array|Net_LDAP2_Error Array with decoded attribute values or Error
+ */
+ public function utf8Decode($attributes)
+ {
+ return $this->utf8($attributes, 'utf8_decode');
+ }
+
+ /**
+ * Encodes or decodes attribute values if needed
+ *
+ * @param array $attributes Array of attributes
+ * @param array $function Function to apply to attribute values
+ *
+ * @access protected
+ * @return array|Net_LDAP2_Error Array of attributes with function applied to values or Error
+ */
+ protected function utf8($attributes, $function)
+ {
+ if (!is_array($attributes) || array_key_exists(0, $attributes)) {
+ return PEAR::raiseError('Parameter $attributes is expected to be an associative array');
+ }
+
+ if (!$this->_schema) {
+ $this->_schema = $this->schema();
+ }
+
+ if (!$this->_link || self::isError($this->_schema) || !function_exists($function)) {
+ return $attributes;
+ }
+
+ if (is_array($attributes) && count($attributes) > 0) {
+
+ foreach ($attributes as $k => $v) {
+
+ if (!isset($this->_schemaAttrs[$k])) {
+
+ $attr = $this->_schema->get('attribute', $k);
+ if (self::isError($attr)) {
+ continue;
+ }
+
+ if (false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
+ $encode = true;
+ } else {
+ $encode = false;
+ }
+ $this->_schemaAttrs[$k] = $encode;
+
+ } else {
+ $encode = $this->_schemaAttrs[$k];
+ }
+
+ if ($encode) {
+ if (is_array($v)) {
+ foreach ($v as $ak => $av) {
+ $v[$ak] = call_user_func($function, $av);
+ }
+ } else {
+ $v = call_user_func($function, $v);
+ }
+ }
+ $attributes[$k] = $v;
+ }
+ }
+ return $attributes;
+ }
+
+ /**
+ * Get the LDAP link resource. It will loop attempting to
+ * re-establish the connection if the connection attempt fails and
+ * auto_reconnect has been turned on (see the _config array documentation).
+ *
+ * @access public
+ * @return resource LDAP link
+ */
+ public function &getLink()
+ {
+ if ($this->_config['auto_reconnect']) {
+ while (true) {
+ //
+ // Return the link handle if we are already connected. Otherwise
+ // try to reconnect.
+ //
+ if ($this->_link !== false) {
+ return $this->_link;
+ } else {
+ $this->performReconnect();
+ }
+ }
+ }
+ return $this->_link;
+ }
+}
+
+/**
+* Net_LDAP2_Error implements a class for reporting portable LDAP error messages.
+*
+* @category Net
+* @package Net_LDAP2
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP22/
+*/
+class Net_LDAP2_Error extends PEAR_Error
+{
+ /**
+ * Net_LDAP2_Error constructor.
+ *
+ * @param string $message String with error message.
+ * @param integer $code Net_LDAP2 error code
+ * @param integer $mode what "error mode" to operate in
+ * @param mixed $level what error level to use for $mode & PEAR_ERROR_TRIGGER
+ * @param mixed $debuginfo additional debug info, such as the last query
+ *
+ * @access public
+ * @see PEAR_Error
+ */
+ public function __construct($message = 'Net_LDAP2_Error', $code = NET_LDAP2_ERROR, $mode = PEAR_ERROR_RETURN,
+ $level = E_USER_NOTICE, $debuginfo = null)
+ {
+ if (is_int($code)) {
+ $this->PEAR_Error($message . ': ' . Net_LDAP2::errorMessage($code), $code, $mode, $level, $debuginfo);
+ } else {
+ $this->PEAR_Error("$message: $code", NET_LDAP2_ERROR, $mode, $level, $debuginfo);
+ }
+ }
+}
+
+?>
diff --git a/extlib/Net/LDAP2/Entry.php b/extlib/Net/LDAP2/Entry.php
new file mode 100644
index 000000000..66de96678
--- /dev/null
+++ b/extlib/Net/LDAP2/Entry.php
@@ -0,0 +1,1055 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_Entry interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Jan Wagner <wagner@netsols.de>
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Tarjej Huse, Jan Wagner, Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: Entry.php 286787 2009-08-04 06:03:12Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+require_once 'Util.php';
+
+/**
+* Object representation of a directory entry
+*
+* This class represents a directory entry. You can add, delete, replace
+* attributes and their values, rename the entry, delete the entry.
+*
+* @category Net
+* @package Net_LDAP2
+* @author Jan Wagner <wagner@netsols.de>
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @author Benedikt Hallinger <beni@php.net>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+class Net_LDAP2_Entry extends PEAR
+{
+ /**
+ * Entry ressource identifier
+ *
+ * @access protected
+ * @var ressource
+ */
+ protected $_entry = null;
+
+ /**
+ * LDAP ressource identifier
+ *
+ * @access protected
+ * @var ressource
+ */
+ protected $_link = null;
+
+ /**
+ * Net_LDAP2 object
+ *
+ * This object will be used for updating and schema checking
+ *
+ * @access protected
+ * @var object Net_LDAP2
+ */
+ protected $_ldap = null;
+
+ /**
+ * Distinguished name of the entry
+ *
+ * @access protected
+ * @var string
+ */
+ protected $_dn = null;
+
+ /**
+ * Attributes
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_attributes = array();
+
+ /**
+ * Original attributes before any modification
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_original = array();
+
+
+ /**
+ * Map of attribute names
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_map = array();
+
+
+ /**
+ * Is this a new entry?
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $_new = true;
+
+ /**
+ * New distinguished name
+ *
+ * @access protected
+ * @var string
+ */
+ protected $_newdn = null;
+
+ /**
+ * Shall the entry be deleted?
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $_delete = false;
+
+ /**
+ * Map with changes to the entry
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_changes = array("add" => array(),
+ "delete" => array(),
+ "replace" => array()
+ );
+ /**
+ * Internal Constructor
+ *
+ * Constructor of the entry. Sets up the distinguished name and the entries
+ * attributes.
+ * You should not call this method manually! Use {@link Net_LDAP2_Entry::createFresh()}
+ * or {@link Net_LDAP2_Entry::createConnected()} instead!
+ *
+ * @param Net_LDAP2|ressource|array &$ldap Net_LDAP2 object, ldap-link ressource or array of attributes
+ * @param string|ressource $entry Either a DN or a LDAP-Entry ressource
+ *
+ * @access protected
+ * @return none
+ */
+ protected function __construct(&$ldap, $entry = null)
+ {
+ $this->PEAR('Net_LDAP2_Error');
+
+ // set up entry resource or DN
+ if (is_resource($entry)) {
+ $this->_entry = &$entry;
+ } else {
+ $this->_dn = $entry;
+ }
+
+ // set up LDAP link
+ if ($ldap instanceof Net_LDAP2) {
+ $this->_ldap = &$ldap;
+ $this->_link = $ldap->getLink();
+ } elseif (is_resource($ldap)) {
+ $this->_link = $ldap;
+ } elseif (is_array($ldap)) {
+ // Special case: here $ldap is an array of attributes,
+ // this means, we have no link. This is a "virtual" entry.
+ // We just set up the attributes so one can work with the object
+ // as expected, but an update() fails unless setLDAP() is called.
+ $this->setAttributes($ldap);
+ }
+
+ // if this is an entry existing in the directory,
+ // then set up as old and fetch attrs
+ if (is_resource($this->_entry) && is_resource($this->_link)) {
+ $this->_new = false;
+ $this->_dn = @ldap_get_dn($this->_link, $this->_entry);
+ $this->setAttributes(); // fetch attributes from server
+ }
+ }
+
+ /**
+ * Creates a fresh entry that may be added to the directory later on
+ *
+ * Use this method, if you want to initialize a fresh entry.
+ *
+ * The method should be called statically: $entry = Net_LDAP2_Entry::createFresh();
+ * You should put a 'objectClass' attribute into the $attrs so the directory server
+ * knows which object you want to create. However, you may omit this in case you
+ * don't want to add this entry to a directory server.
+ *
+ * The attributes parameter is as following:
+ * <code>
+ * $attrs = array( 'attribute1' => array('value1', 'value2'),
+ * 'attribute2' => 'single value'
+ * );
+ * </code>
+ *
+ * @param string $dn DN of the Entry
+ * @param array $attrs Attributes of the entry
+ *
+ * @static
+ * @return Net_LDAP2_Entry|Net_LDAP2_Error
+ */
+ public static function createFresh($dn, $attrs = array())
+ {
+ if (!is_array($attrs)) {
+ return PEAR::raiseError("Unable to create fresh entry: Parameter \$attrs needs to be an array!");
+ }
+
+ $entry = new Net_LDAP2_Entry($attrs, $dn);
+ return $entry;
+ }
+
+ /**
+ * Creates a Net_LDAP2_Entry object out of an ldap entry resource
+ *
+ * Use this method, if you want to initialize an entry object that is
+ * already present in some directory and that you have read manually.
+ *
+ * Please note, that if you want to create an entry object that represents
+ * some already existing entry, you should use {@link createExisting()}.
+ *
+ * The method should be called statically: $entry = Net_LDAP2_Entry::createConnected();
+ *
+ * @param Net_LDAP2 $ldap Net_LDA2 object
+ * @param resource $entry PHP LDAP entry resource
+ *
+ * @static
+ * @return Net_LDAP2_Entry|Net_LDAP2_Error
+ */
+ public static function createConnected($ldap, $entry)
+ {
+ if (!$ldap instanceof Net_LDAP2) {
+ return PEAR::raiseError("Unable to create connected entry: Parameter \$ldap needs to be a Net_LDAP2 object!");
+ }
+ if (!is_resource($entry)) {
+ return PEAR::raiseError("Unable to create connected entry: Parameter \$entry needs to be a ldap entry resource!");
+ }
+
+ $entry = new Net_LDAP2_Entry($ldap, $entry);
+ return $entry;
+ }
+
+ /**
+ * Creates an Net_LDAP2_Entry object that is considered already existing
+ *
+ * Use this method, if you want to modify an already existing entry
+ * without fetching it first.
+ * In most cases however, it is better to fetch the entry via Net_LDAP2->getEntry()!
+ *
+ * Please note that you should take care if you construct entries manually with this
+ * because you may get weird synchronisation problems.
+ * The attributes and values as well as the entry itself are considered existent
+ * which may produce errors if you try to modify an entry which doesn't really exist
+ * or if you try to overwrite some attribute with an value already present.
+ *
+ * This method is equal to calling createFresh() and after that markAsNew(FALSE).
+ *
+ * The method should be called statically: $entry = Net_LDAP2_Entry::createExisting();
+ *
+ * The attributes parameter is as following:
+ * <code>
+ * $attrs = array( 'attribute1' => array('value1', 'value2'),
+ * 'attribute2' => 'single value'
+ * );
+ * </code>
+ *
+ * @param string $dn DN of the Entry
+ * @param array $attrs Attributes of the entry
+ *
+ * @static
+ * @return Net_LDAP2_Entry|Net_LDAP2_Error
+ */
+ public static function createExisting($dn, $attrs = array())
+ {
+ if (!is_array($attrs)) {
+ return PEAR::raiseError("Unable to create entry object: Parameter \$attrs needs to be an array!");
+ }
+
+ $entry = Net_LDAP2_Entry::createFresh($dn, $attrs);
+ if ($entry instanceof Net_LDAP2_Error) {
+ return $entry;
+ } else {
+ $entry->markAsNew(false);
+ return $entry;
+ }
+ }
+
+ /**
+ * Get or set the distinguished name of the entry
+ *
+ * If called without an argument the current (or the new DN if set) DN gets returned.
+ * If you provide an DN, this entry is moved to the new location specified if a DN existed.
+ * If the DN was not set, the DN gets initialized. Call {@link update()} to actually create
+ * the new Entry in the directory.
+ * To fetch the current active DN after setting a new DN but before an update(), you can use
+ * {@link currentDN()} to retrieve the DN that is currently active.
+ *
+ * Please note that special characters (eg german umlauts) should be encoded using utf8_encode().
+ * You may use {@link Net_LDAP2_Util::canonical_dn()} for properly encoding of the DN.
+ *
+ * @param string $dn New distinguished name
+ *
+ * @access public
+ * @return string|true Distinguished name (or true if a new DN was provided)
+ */
+ public function dn($dn = null)
+ {
+ if (false == is_null($dn)) {
+ if (is_null($this->_dn)) {
+ $this->_dn = $dn;
+ } else {
+ $this->_newdn = $dn;
+ }
+ return true;
+ }
+ return (isset($this->_newdn) ? $this->_newdn : $this->currentDN());
+ }
+
+ /**
+ * Renames or moves the entry
+ *
+ * This is just a convinience alias to {@link dn()}
+ * to make your code more meaningful.
+ *
+ * @param string $newdn The new DN
+ *
+ * @return true
+ */
+ public function move($newdn)
+ {
+ return $this->dn($newdn);
+ }
+
+ /**
+ * Sets the internal attributes array
+ *
+ * This fetches the values for the attributes from the server.
+ * The attribute Syntax will be checked so binary attributes will be returned
+ * as binary values.
+ *
+ * Attributes may be passed directly via the $attributes parameter to setup this
+ * entry manually. This overrides attribute fetching from the server.
+ *
+ * @param array $attributes Attributes to set for this entry
+ *
+ * @access protected
+ * @return void
+ */
+ protected function setAttributes($attributes = null)
+ {
+ /*
+ * fetch attributes from the server
+ */
+ if (is_null($attributes) && is_resource($this->_entry) && is_resource($this->_link)) {
+ // fetch schema
+ if ($this->_ldap instanceof Net_LDAP2) {
+ $schema =& $this->_ldap->schema();
+ }
+ // fetch attributes
+ $attributes = array();
+ do {
+ if (empty($attr)) {
+ $ber = null;
+ $attr = @ldap_first_attribute($this->_link, $this->_entry, $ber);
+ } else {
+ $attr = @ldap_next_attribute($this->_link, $this->_entry, $ber);
+ }
+ if ($attr) {
+ $func = 'ldap_get_values'; // standard function to fetch value
+
+ // Try to get binary values as binary data
+ if ($schema instanceof Net_LDAP2_Schema) {
+ if ($schema->isBinary($attr)) {
+ $func = 'ldap_get_values_len';
+ }
+ }
+ // fetch attribute value (needs error checking?)
+ $attributes[$attr] = $func($this->_link, $this->_entry, $attr);
+ }
+ } while ($attr);
+ }
+
+ /*
+ * set attribute data directly, if passed
+ */
+ if (is_array($attributes) && count($attributes) > 0) {
+ if (isset($attributes["count"]) && is_numeric($attributes["count"])) {
+ unset($attributes["count"]);
+ }
+ foreach ($attributes as $k => $v) {
+ // attribute names should not be numeric
+ if (is_numeric($k)) {
+ continue;
+ }
+ // map generic attribute name to real one
+ $this->_map[strtolower($k)] = $k;
+ // attribute values should be in an array
+ if (false == is_array($v)) {
+ $v = array($v);
+ }
+ // remove the value count (comes from ldap server)
+ if (isset($v["count"])) {
+ unset($v["count"]);
+ }
+ $this->_attributes[$k] = $v;
+ }
+ }
+
+ // save a copy for later use
+ $this->_original = $this->_attributes;
+ }
+
+ /**
+ * Get the values of all attributes in a hash
+ *
+ * The returned hash has the form
+ * <code>array('attributename' => 'single value',
+ * 'attributename' => array('value1', value2', value3'))</code>
+ *
+ * @access public
+ * @return array Hash of all attributes with their values
+ */
+ public function getValues()
+ {
+ $attrs = array();
+ foreach ($this->_attributes as $attr => $value) {
+ $attrs[$attr] = $this->getValue($attr);
+ }
+ return $attrs;
+ }
+
+ /**
+ * Get the value of a specific attribute
+ *
+ * The first parameter is the name of the attribute
+ * The second parameter influences the way the value is returned:
+ * 'single': only the first value is returned as string
+ * 'all': all values including the value count are returned in an
+ * array
+ * 'default': in all other cases an attribute value with a single value is
+ * returned as string, if it has multiple values it is returned
+ * as an array (without value count)
+ *
+ * @param string $attr Attribute name
+ * @param string $option Option
+ *
+ * @access public
+ * @return string|array|PEAR_Error string, array or PEAR_Error
+ */
+ public function getValue($attr, $option = null)
+ {
+ $attr = $this->getAttrName($attr);
+
+ if (false == array_key_exists($attr, $this->_attributes)) {
+ return PEAR::raiseError("Unknown attribute ($attr) requested");
+ }
+
+ $value = $this->_attributes[$attr];
+
+ if ($option == "single" || (count($value) == 1 && $option != 'all')) {
+ $value = array_shift($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Alias function of getValue for perl-ldap interface
+ *
+ * @see getValue()
+ * @return string|array|PEAR_Error
+ */
+ public function get_value()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'getValue' ), $args);
+ }
+
+ /**
+ * Returns an array of attributes names
+ *
+ * @access public
+ * @return array Array of attribute names
+ */
+ public function attributes()
+ {
+ return array_keys($this->_attributes);
+ }
+
+ /**
+ * Returns whether an attribute exists or not
+ *
+ * @param string $attr Attribute name
+ *
+ * @access public
+ * @return boolean
+ */
+ public function exists($attr)
+ {
+ $attr = $this->getAttrName($attr);
+ return array_key_exists($attr, $this->_attributes);
+ }
+
+ /**
+ * Adds a new attribute or a new value to an existing attribute
+ *
+ * The paramter has to be an array of the form:
+ * array('attributename' => 'single value',
+ * 'attributename' => array('value1', 'value2))
+ * When the attribute already exists the values will be added, else the
+ * attribute will be created. These changes are local to the entry and do
+ * not affect the entry on the server until update() is called.
+ *
+ * Note, that you can add values of attributes that you haven't selected, but if
+ * you do so, {@link getValue()} and {@link getValues()} will only return the
+ * values you added, _NOT_ all values present on the server. To avoid this, just refetch
+ * the entry after calling {@link update()} or select the attribute.
+ *
+ * @param array $attr Attributes to add
+ *
+ * @access public
+ * @return true|Net_LDAP2_Error
+ */
+ public function add($attr = array())
+ {
+ if (false == is_array($attr)) {
+ return PEAR::raiseError("Parameter must be an array");
+ }
+ foreach ($attr as $k => $v) {
+ $k = $this->getAttrName($k);
+ if (false == is_array($v)) {
+ // Do not add empty values
+ if ($v == null) {
+ continue;
+ } else {
+ $v = array($v);
+ }
+ }
+ // add new values to existing attribute or add new attribute
+ if ($this->exists($k)) {
+ $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
+ } else {
+ $this->_map[strtolower($k)] = $k;
+ $this->_attributes[$k] = $v;
+ }
+ // save changes for update()
+ if (empty($this->_changes["add"][$k])) {
+ $this->_changes["add"][$k] = array();
+ }
+ $this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
+ }
+ $return = true;
+ return $return;
+ }
+
+ /**
+ * Deletes an whole attribute or a value or the whole entry
+ *
+ * The parameter can be one of the following:
+ *
+ * "attributename" - The attribute as a whole will be deleted
+ * array("attributename1", "attributename2) - All given attributes will be
+ * deleted
+ * array("attributename" => "value") - The value will be deleted
+ * array("attributename" => array("value1", "value2") - The given values
+ * will be deleted
+ * If $attr is null or omitted , then the whole Entry will be deleted!
+ *
+ * These changes are local to the entry and do
+ * not affect the entry on the server until {@link update()} is called.
+ *
+ * Please note that you must select the attribute (at $ldap->search() for example)
+ * to be able to delete values of it, Otherwise {@link update()} will silently fail
+ * and remove nothing.
+ *
+ * @param string|array $attr Attributes to delete (NULL or missing to delete whole entry)
+ *
+ * @access public
+ * @return true
+ */
+ public function delete($attr = null)
+ {
+ if (is_null($attr)) {
+ $this->_delete = true;
+ return true;
+ }
+ if (is_string($attr)) {
+ $attr = array($attr);
+ }
+ // Make the assumption that attribute names cannot be numeric,
+ // therefore this has to be a simple list of attribute names to delete
+ if (is_numeric(key($attr))) {
+ foreach ($attr as $name) {
+ if (is_array($name)) {
+ // someone mixed modes (list mode but specific values given!)
+ $del_attr_name = array_search($name, $attr);
+ $this->delete(array($del_attr_name => $name));
+ } else {
+ // mark for update() if this attr was not marked before
+ $name = $this->getAttrName($name);
+ if ($this->exists($name)) {
+ $this->_changes["delete"][$name] = null;
+ unset($this->_attributes[$name]);
+ }
+ }
+ }
+ } else {
+ // Here we have a hash with "attributename" => "value to delete"
+ foreach ($attr as $name => $values) {
+ if (is_int($name)) {
+ // someone mixed modes and gave us just an attribute name
+ $this->delete($values);
+ } else {
+ // mark for update() if this attr was not marked before;
+ // this time it must consider the selected values also
+ $name = $this->getAttrName($name);
+ if ($this->exists($name)) {
+ if (false == is_array($values)) {
+ $values = array($values);
+ }
+ // save values to be deleted
+ if (empty($this->_changes["delete"][$name])) {
+ $this->_changes["delete"][$name] = array();
+ }
+ $this->_changes["delete"][$name] =
+ array_unique(array_merge($this->_changes["delete"][$name], $values));
+ foreach ($values as $value) {
+ // find the key for the value that should be deleted
+ $key = array_search($value, $this->_attributes[$name]);
+ if (false !== $key) {
+ // delete the value
+ unset($this->_attributes[$name][$key]);
+ }
+ }
+ }
+ }
+ }
+ }
+ $return = true;
+ return $return;
+ }
+
+ /**
+ * Replaces attributes or its values
+ *
+ * The parameter has to an array of the following form:
+ * array("attributename" => "single value",
+ * "attribute2name" => array("value1", "value2"),
+ * "deleteme1" => null,
+ * "deleteme2" => "")
+ * If the attribute does not yet exist it will be added instead (see also $force).
+ * If the attribue value is null, the attribute will de deleted.
+ *
+ * These changes are local to the entry and do
+ * not affect the entry on the server until {@link update()} is called.
+ *
+ * In some cases you are not allowed to read the attributes value (for
+ * example the ActiveDirectory attribute unicodePwd) but are allowed to
+ * replace the value. In this case replace() would assume that the attribute
+ * is not in the directory yet and tries to add it which will result in an
+ * LDAP_TYPE_OR_VALUE_EXISTS error.
+ * To force replace mode instead of add, you can set $force to true.
+ *
+ * @param array $attr Attributes to replace
+ * @param bool $force Force replacing mode in case we can't read the attr value but are allowed to replace it
+ *
+ * @access public
+ * @return true|Net_LDAP2_Error
+ */
+ public function replace($attr = array(), $force = false)
+ {
+ if (false == is_array($attr)) {
+ return PEAR::raiseError("Parameter must be an array");
+ }
+ foreach ($attr as $k => $v) {
+ $k = $this->getAttrName($k);
+ if (false == is_array($v)) {
+ // delete attributes with empty values; treat ints as string
+ if (is_int($v)) {
+ $v = "$v";
+ }
+ if ($v == null) {
+ $this->delete($k);
+ continue;
+ } else {
+ $v = array($v);
+ }
+ }
+ // existing attributes will get replaced
+ if ($this->exists($k) || $force) {
+ $this->_changes["replace"][$k] = $v;
+ $this->_attributes[$k] = $v;
+ } else {
+ // new ones just get added
+ $this->add(array($k => $v));
+ }
+ }
+ $return = true;
+ return $return;
+ }
+
+ /**
+ * Update the entry on the directory server
+ *
+ * This will evaluate all changes made so far and send them
+ * to the directory server.
+ * Please note, that if you make changes to objectclasses wich
+ * have mandatory attributes set, update() will currently fail.
+ * Remove the entry from the server and readd it as new in such cases.
+ * This also will deal with problems with setting structural object classes.
+ *
+ * @param Net_LDAP2 $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance
+ *
+ * @access public
+ * @return true|Net_LDAP2_Error
+ * @todo Entry rename with a DN containing special characters needs testing!
+ */
+ public function update($ldap = null)
+ {
+ if ($ldap) {
+ $msg = $this->setLDAP($ldap);
+ if (Net_LDAP2::isError($msg)) {
+ return PEAR::raiseError('You passed an invalid $ldap variable to update()');
+ }
+ }
+
+ // ensure we have a valid LDAP object
+ $ldap =& $this->getLDAP();
+ if (!$ldap instanceof Net_LDAP2) {
+ return PEAR::raiseError("The entries LDAP object is not valid");
+ }
+
+ // Get and check link
+ $link = $ldap->getLink();
+ if (!is_resource($link)) {
+ return PEAR::raiseError("Could not update entry: internal LDAP link is invalid");
+ }
+
+ /*
+ * Delete the entry
+ */
+ if (true === $this->_delete) {
+ return $ldap->delete($this);
+ }
+
+ /*
+ * New entry
+ */
+ if (true === $this->_new) {
+ $msg = $ldap->add($this);
+ if (Net_LDAP2::isError($msg)) {
+ return $msg;
+ }
+ $this->_new = false;
+ $this->_changes['add'] = array();
+ $this->_changes['delete'] = array();
+ $this->_changes['replace'] = array();
+ $this->_original = $this->_attributes;
+
+ $return = true;
+ return $return;
+ }
+
+ /*
+ * Rename/move entry
+ */
+ if (false == is_null($this->_newdn)) {
+ if ($ldap->getLDAPVersion() !== 3) {
+ return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3");
+ }
+ // make dn relative to parent (needed for ldap rename)
+ $parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
+ if (Net_LDAP2::isError($parent)) {
+ return $parent;
+ }
+ $child = array_shift($parent);
+ // maybe the dn consist of a multivalued RDN, we must build the dn in this case
+ // because the $child-RDN is an array!
+ if (is_array($child)) {
+ $child = Net_LDAP2_Util::canonical_dn($child);
+ }
+ $parent = Net_LDAP2_Util::canonical_dn($parent);
+
+ // rename/move
+ if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) {
+ return PEAR::raiseError("Entry not renamed: " .
+ @ldap_error($link), @ldap_errno($link));
+ }
+ // reflect changes to local copy
+ $this->_dn = $this->_newdn;
+ $this->_newdn = null;
+ }
+
+ /*
+ * Carry out modifications to the entry
+ */
+ // ADD
+ foreach ($this->_changes["add"] as $attr => $value) {
+ // if attribute exists, add new values
+ if ($this->exists($attr)) {
+ if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) {
+ return PEAR::raiseError("Could not add new values to attribute $attr: " .
+ @ldap_error($link), @ldap_errno($link));
+ }
+ } else {
+ // new attribute
+ if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
+ return PEAR::raiseError("Could not add new attribute $attr: " .
+ @ldap_error($link), @ldap_errno($link));
+ }
+ }
+ // all went well here, I guess
+ unset($this->_changes["add"][$attr]);
+ }
+
+ // DELETE
+ foreach ($this->_changes["delete"] as $attr => $value) {
+ // In LDAPv3 you need to specify the old values for deleting
+ if (is_null($value) && $ldap->getLDAPVersion() === 3) {
+ $value = $this->_original[$attr];
+ }
+ if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) {
+ return PEAR::raiseError("Could not delete attribute $attr: " .
+ @ldap_error($link), @ldap_errno($link));
+ }
+ unset($this->_changes["delete"][$attr]);
+ }
+
+ // REPLACE
+ foreach ($this->_changes["replace"] as $attr => $value) {
+ if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
+ return PEAR::raiseError("Could not replace attribute $attr values: " .
+ @ldap_error($link), @ldap_errno($link));
+ }
+ unset($this->_changes["replace"][$attr]);
+ }
+
+ // all went well, so _original (server) becomes _attributes (local copy)
+ $this->_original = $this->_attributes;
+
+ $return = true;
+ return $return;
+ }
+
+ /**
+ * Returns the right attribute name
+ *
+ * @param string $attr Name of attribute
+ *
+ * @access protected
+ * @return string The right name of the attribute
+ */
+ protected function getAttrName($attr)
+ {
+ $name = strtolower($attr);
+ if (array_key_exists($name, $this->_map)) {
+ $attr = $this->_map[$name];
+ }
+ return $attr;
+ }
+
+ /**
+ * Returns a reference to the LDAP-Object of this entry
+ *
+ * @access public
+ * @return Net_LDAP2|Net_LDAP2_Error Reference to the Net_LDAP2 Object (the connection) or Net_LDAP2_Error
+ */
+ public function &getLDAP()
+ {
+ if (!$this->_ldap instanceof Net_LDAP2) {
+ $err = new PEAR_Error('LDAP is not a valid Net_LDAP2 object');
+ return $err;
+ } else {
+ return $this->_ldap;
+ }
+ }
+
+ /**
+ * Sets a reference to the LDAP-Object of this entry
+ *
+ * After setting a Net_LDAP2 object, calling update() will use that object for
+ * updating directory contents. Use this to dynamicly switch directorys.
+ *
+ * @param Net_LDAP2 &$ldap Net_LDAP2 object that this entry should be connected to
+ *
+ * @access public
+ * @return true|Net_LDAP2_Error
+ */
+ public function setLDAP(&$ldap)
+ {
+ if (!$ldap instanceof Net_LDAP2) {
+ return PEAR::raiseError("LDAP is not a valid Net_LDAP2 object");
+ } else {
+ $this->_ldap =& $ldap;
+ return true;
+ }
+ }
+
+ /**
+ * Marks the entry as new/existing.
+ *
+ * If an Entry is marked as new, it will be added to the directory
+ * when calling {@link update()}.
+ * If the entry is marked as old ($mark = false), then the entry is
+ * assumed to be present in the directory server wich results in
+ * modification when calling {@link update()}.
+ *
+ * @param boolean $mark Value to set, defaults to "true"
+ *
+ * @return void
+ */
+ public function markAsNew($mark = true)
+ {
+ $this->_new = ($mark)? true : false;
+ }
+
+ /**
+ * Applies a regular expression onto a single- or multivalued attribute (like preg_match())
+ *
+ * This method behaves like PHPs preg_match() but with some exceptions.
+ * If you want to retrieve match information, then you MUST pass the
+ * $matches parameter via reference! otherwise you will get no matches.
+ * Since it is possible to have multi valued attributes the $matches
+ * array will have a additionally numerical dimension (one for each value):
+ * <code>
+ * $matches = array(
+ * 0 => array (usual preg_match() returnarray),
+ * 1 => array (usual preg_match() returnarray)
+ * )
+ * </code>
+ * Please note, that $matches will be initialized to an empty array inside.
+ *
+ * Usage example:
+ * <code>
+ * $result = $entry->preg_match('/089(\d+)/', 'telephoneNumber', &$matches);
+ * if ( $result === true ){
+ * echo "First match: ".$matches[0][1]; // Match of value 1, content of first bracket
+ * } else {
+ * if ( Net_LDAP2::isError($result) ) {
+ * echo "Error: ".$result->getMessage();
+ * } else {
+ * echo "No match found.";
+ * }
+ * }
+ * </code>
+ *
+ * Please note that it is important to test for an Net_LDAP2_Error, because objects are
+ * evaluating to true by default, thus if an error occured, and you only check using "==" then
+ * you get misleading results. Use the "identical" (===) operator to test for matches to
+ * avoid this as shown above.
+ *
+ * @param string $regex The regular expression
+ * @param string $attr_name The attribute to search in
+ * @param array $matches (optional, PASS BY REFERENCE!) Array to store matches in
+ *
+ * @return boolean|Net_LDAP2_Error TRUE, if we had a match in one of the values, otherwise false. Net_LDAP2_Error in case something went wrong
+ */
+ public function pregMatch($regex, $attr_name, $matches = array())
+ {
+ $matches = array();
+
+ // fetch attribute values
+ $attr = $this->getValue($attr_name, 'all');
+ if (Net_LDAP2::isError($attr)) {
+ return $attr;
+ } else {
+ unset($attr['count']);
+ }
+
+ // perform preg_match() on all values
+ $match = false;
+ foreach ($attr as $thisvalue) {
+ $matches_int = array();
+ if (preg_match($regex, $thisvalue, $matches_int)) {
+ $match = true;
+ array_push($matches, $matches_int); // store matches in reference
+ }
+ }
+ return $match;
+ }
+
+ /**
+ * Alias of {@link pregMatch()} for compatibility to Net_LDAP 1
+ *
+ * @see pregMatch()
+ * @return boolean|Net_LDAP2_Error
+ */
+ public function preg_match()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'pregMatch' ), $args);
+ }
+
+ /**
+ * Tells if the entry is consiedered as new (not present in the server)
+ *
+ * Please note, that this doesn't tell you if the entry is present on the server.
+ * Use {@link Net_LDAP2::dnExists()} to see if an entry is already there.
+ *
+ * @return boolean
+ */
+ public function isNew()
+ {
+ return $this->_new;
+ }
+
+
+ /**
+ * Is this entry going to be deleted once update() is called?
+ *
+ * @return boolean
+ */
+ public function willBeDeleted()
+ {
+ return $this->_delete;
+ }
+
+ /**
+ * Is this entry going to be moved once update() is called?
+ *
+ * @return boolean
+ */
+ public function willBeMoved()
+ {
+ return ($this->dn() !== $this->currentDN());
+ }
+
+ /**
+ * Returns always the original DN
+ *
+ * If an entry will be moved but {@link update()} was not called,
+ * {@link dn()} will return the new DN. This method however, returns
+ * always the current active DN.
+ *
+ * @return string
+ */
+ public function currentDN()
+ {
+ return $this->_dn;
+ }
+
+ /**
+ * Returns the attribute changes to be carried out once update() is called
+ *
+ * @return array
+ */
+ public function getChanges()
+ {
+ return $this->_changes;
+ }
+}
+?>
diff --git a/extlib/Net/LDAP2/Filter.php b/extlib/Net/LDAP2/Filter.php
new file mode 100644
index 000000000..0723edab2
--- /dev/null
+++ b/extlib/Net/LDAP2/Filter.php
@@ -0,0 +1,514 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_Filter interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: Filter.php 289978 2009-10-27 09:56:41Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+require_once 'Util.php';
+
+/**
+* Object representation of a part of a LDAP filter.
+*
+* This Class is not completely compatible to the PERL interface!
+*
+* The purpose of this class is, that users can easily build LDAP filters
+* without having to worry about right escaping etc.
+* A Filter is built using several independent filter objects
+* which are combined afterwards. This object works in two
+* modes, depending how the object is created.
+* If the object is created using the {@link create()} method, then this is a leaf-object.
+* If the object is created using the {@link combine()} method, then this is a container object.
+*
+* LDAP filters are defined in RFC-2254 and can be found under
+* {@link http://www.ietf.org/rfc/rfc2254.txt}
+*
+* Here a quick copy&paste example:
+* <code>
+* $filter0 = Net_LDAP2_Filter::create('stars', 'equals', '***');
+* $filter_not0 = Net_LDAP2_Filter::combine('not', $filter0);
+*
+* $filter1 = Net_LDAP2_Filter::create('gn', 'begins', 'bar');
+* $filter2 = Net_LDAP2_Filter::create('gn', 'ends', 'baz');
+* $filter_comp = Net_LDAP2_Filter::combine('or',array($filter_not0, $filter1, $filter2));
+*
+* echo $filter_comp->asString();
+* // This will output: (|(!(stars=\0x5c0x2a\0x5c0x2a\0x5c0x2a))(gn=bar*)(gn=*baz))
+* // The stars in $filter0 are treaten as real stars unless you disable escaping.
+* </code>
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+class Net_LDAP2_Filter extends PEAR
+{
+ /**
+ * Storage for combination of filters
+ *
+ * This variable holds a array of filter objects
+ * that should be combined by this filter object.
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_subfilters = array();
+
+ /**
+ * Match of this filter
+ *
+ * If this is a leaf filter, then a matching rule is stored,
+ * if it is a container, then it is a logical operator
+ *
+ * @access protected
+ * @var string
+ */
+ protected $_match;
+
+ /**
+ * Single filter
+ *
+ * If we operate in leaf filter mode,
+ * then the constructing method stores
+ * the filter representation here
+ *
+ * @acces private
+ * @var string
+ */
+ protected $_filter;
+
+ /**
+ * Create a new Net_LDAP2_Filter object and parse $filter.
+ *
+ * This is for PERL Net::LDAP interface.
+ * Construction of Net_LDAP2_Filter objects should happen through either
+ * {@link create()} or {@link combine()} which give you more control.
+ * However, you may use the perl iterface if you already have generated filters.
+ *
+ * @param string $filter LDAP filter string
+ *
+ * @see parse()
+ */
+ public function __construct($filter = false)
+ {
+ // The optional parameter must remain here, because otherwise create() crashes
+ if (false !== $filter) {
+ $filter_o = self::parse($filter);
+ if (PEAR::isError($filter_o)) {
+ $this->_filter = $filter_o; // assign error, so asString() can report it
+ } else {
+ $this->_filter = $filter_o->asString();
+ }
+ }
+ }
+
+ /**
+ * Constructor of a new part of a LDAP filter.
+ *
+ * The following matching rules exists:
+ * - equals: One of the attributes values is exactly $value
+ * Please note that case sensitiviness is depends on the
+ * attributes syntax configured in the server.
+ * - begins: One of the attributes values must begin with $value
+ * - ends: One of the attributes values must end with $value
+ * - contains: One of the attributes values must contain $value
+ * - present | any: The attribute can contain any value but must be existent
+ * - greater: The attributes value is greater than $value
+ * - less: The attributes value is less than $value
+ * - greaterOrEqual: The attributes value is greater or equal than $value
+ * - lessOrEqual: The attributes value is less or equal than $value
+ * - approx: One of the attributes values is similar to $value
+ *
+ * If $escape is set to true (default) then $value will be escaped
+ * properly. If it is set to false then $value will be treaten as raw filter value string.
+ * You should escape yourself using {@link Net_LDAP2_Util::escape_filter_value()}!
+ *
+ * Examples:
+ * <code>
+ * // This will find entries that contain an attribute "sn" that ends with "foobar":
+ * $filter = new Net_LDAP2_Filter('sn', 'ends', 'foobar');
+ *
+ * // This will find entries that contain an attribute "sn" that has any value set:
+ * $filter = new Net_LDAP2_Filter('sn', 'any');
+ * </code>
+ *
+ * @param string $attr_name Name of the attribute the filter should apply to
+ * @param string $match Matching rule (equals, begins, ends, contains, greater, less, greaterOrEqual, lessOrEqual, approx, any)
+ * @param string $value (optional) if given, then this is used as a filter
+ * @param boolean $escape Should $value be escaped? (default: yes, see {@link Net_LDAP2_Util::escape_filter_value()} for detailed information)
+ *
+ * @return Net_LDAP2_Filter|Net_LDAP2_Error
+ */
+ public static function &create($attr_name, $match, $value = '', $escape = true)
+ {
+ $leaf_filter = new Net_LDAP2_Filter();
+ if ($escape) {
+ $array = Net_LDAP2_Util::escape_filter_value(array($value));
+ $value = $array[0];
+ }
+ switch (strtolower($match)) {
+ case 'equals':
+ $leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
+ break;
+ case 'begins':
+ $leaf_filter->_filter = '(' . $attr_name . '=' . $value . '*)';
+ break;
+ case 'ends':
+ $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . ')';
+ break;
+ case 'contains':
+ $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
+ break;
+ case 'greater':
+ $leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
+ break;
+ case 'less':
+ $leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
+ break;
+ case 'greaterorequal':
+ case '>=':
+ $leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')';
+ break;
+ case 'lessorequal':
+ case '<=':
+ $leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')';
+ break;
+ case 'approx':
+ case '~=':
+ $leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')';
+ break;
+ case 'any':
+ case 'present': // alias that may improve user code readability
+ $leaf_filter->_filter = '(' . $attr_name . '=*)';
+ break;
+ default:
+ return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
+ }
+ return $leaf_filter;
+ }
+
+ /**
+ * Combine two or more filter objects using a logical operator
+ *
+ * This static method combines two or more filter objects and returns one single
+ * filter object that contains all the others.
+ * Call this method statically: $filter = Net_LDAP2_Filter('or', array($filter1, $filter2))
+ * If the array contains filter strings instead of filter objects, we will try to parse them.
+ *
+ * @param string $log_op The locicall operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
+ * @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
+ *
+ * @return Net_LDAP2_Filter|Net_LDAP2_Error
+ * @static
+ */
+ public static function &combine($log_op, $filters)
+ {
+ if (PEAR::isError($filters)) {
+ return $filters;
+ }
+
+ // substitude named operators to logical operators
+ if ($log_op == 'and') $log_op = '&';
+ if ($log_op == 'or') $log_op = '|';
+ if ($log_op == 'not') $log_op = '!';
+
+ // tests for sane operation
+ if ($log_op == '!') {
+ // Not-combination, here we only accept one filter object or filter string
+ if ($filters instanceof Net_LDAP2_Filter) {
+ $filters = array($filters); // force array
+ } elseif (is_string($filters)) {
+ $filter_o = self::parse($filters);
+ if (PEAR::isError($filter_o)) {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: '.$filter_o->getMessage());
+ return $err;
+ } else {
+ $filters = array($filter_o);
+ }
+ } elseif (is_array($filters)) {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
+ return $err;
+ } else {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
+ return $err;
+ }
+ } elseif ($log_op == '&' || $log_op == '|') {
+ if (!is_array($filters) || count($filters) < 2) {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP2_Filter objects!');
+ return $err;
+ }
+ } else {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: logical operator is not known!');
+ return $err;
+ }
+
+ $combined_filter = new Net_LDAP2_Filter();
+ foreach ($filters as $key => $testfilter) { // check for errors
+ if (PEAR::isError($testfilter)) {
+ return $testfilter;
+ } elseif (is_string($testfilter)) {
+ // string found, try to parse into an filter object
+ $filter_o = self::parse($testfilter);
+ if (PEAR::isError($filter_o)) {
+ return $filter_o;
+ } else {
+ $filters[$key] = $filter_o;
+ }
+ } elseif (!$testfilter instanceof Net_LDAP2_Filter) {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: invalid object passed in array $filters!');
+ return $err;
+ }
+ }
+
+ $combined_filter->_subfilters = $filters;
+ $combined_filter->_match = $log_op;
+ return $combined_filter;
+ }
+
+ /**
+ * Parse FILTER into a Net_LDAP2_Filter object
+ *
+ * This parses an filter string into Net_LDAP2_Filter objects.
+ *
+ * @param string $FILTER The filter string
+ *
+ * @access static
+ * @return Net_LDAP2_Filter|Net_LDAP2_Error
+ * @todo Leaf-mode: Do we need to escape at all? what about *-chars?check for the need of encoding values, tackle problems (see code comments)
+ */
+ public static function parse($FILTER)
+ {
+ if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
+ if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
+ // Subfilter processing: pass subfilters to parse() and combine
+ // the objects using the logical operator detected
+ // we have now something like "&(...)(...)(...)" but at least one part ("!(...)").
+ // Each subfilter could be an arbitary complex subfilter.
+
+ // extract logical operator and filter arguments
+ $log_op = substr($matches[1], 0, 1);
+ $remaining_component = substr($matches[1], 1);
+
+ // split $remaining_component into individual subfilters
+ // we cannot use split() for this, because we do not know the
+ // complexiness of the subfilter. Thus, we look trough the filter
+ // string and just recognize ending filters at the first level.
+ // We record the index number of the char and use that information
+ // later to split the string.
+ $sub_index_pos = array();
+ $prev_char = ''; // previous character looked at
+ $level = 0; // denotes the current bracket level we are,
+ // >1 is too deep, 1 is ok, 0 is outside any
+ // subcomponent
+ for ($curpos = 0; $curpos < strlen($remaining_component); $curpos++) {
+ $cur_char = substr($remaining_component, $curpos, 1);
+
+ // rise/lower bracket level
+ if ($cur_char == '(' && $prev_char != '\\') {
+ $level++;
+ } elseif ($cur_char == ')' && $prev_char != '\\') {
+ $level--;
+ }
+
+ if ($cur_char == '(' && $prev_char == ')' && $level == 1) {
+ array_push($sub_index_pos, $curpos); // mark the position for splitting
+ }
+ $prev_char = $cur_char;
+ }
+
+ // now perform the splits. To get also the last part, we
+ // need to add the "END" index to the split array
+ array_push($sub_index_pos, strlen($remaining_component));
+ $subfilters = array();
+ $oldpos = 0;
+ foreach ($sub_index_pos as $s_pos) {
+ $str_part = substr($remaining_component, $oldpos, $s_pos - $oldpos);
+ array_push($subfilters, $str_part);
+ $oldpos = $s_pos;
+ }
+
+ // some error checking...
+ if (count($subfilters) == 1) {
+ // only one subfilter found
+ } elseif (count($subfilters) > 1) {
+ // several subfilters found
+ if ($log_op == "!") {
+ return PEAR::raiseError("Filter parsing error: invalid filter syntax - NOT operator detected but several arguments given!");
+ }
+ } else {
+ // this should not happen unless the user specified a wrong filter
+ return PEAR::raiseError("Filter parsing error: invalid filter syntax - got operator '$log_op' but no argument!");
+ }
+
+ // Now parse the subfilters into objects and combine them using the operator
+ $subfilters_o = array();
+ foreach ($subfilters as $s_s) {
+ $o = self::parse($s_s);
+ if (PEAR::isError($o)) {
+ return $o;
+ } else {
+ array_push($subfilters_o, self::parse($s_s));
+ }
+ }
+
+ $filter_o = self::combine($log_op, $subfilters_o);
+ return $filter_o;
+
+ } else {
+ // This is one leaf filter component, do some syntax checks, then escape and build filter_o
+ // $matches[1] should be now something like "foo=bar"
+
+ // detect multiple leaf components
+ // [TODO] Maybe this will make problems with filters containing brackets inside the value
+ if (stristr($matches[1], ')(')) {
+ return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
+ } else {
+ $filter_parts = preg_split('/(?<!\\\\)(=|=~|>|<|>=|<=)/', $matches[1], 2, PREG_SPLIT_DELIM_CAPTURE);
+ if (count($filter_parts) != 3) {
+ return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
+ } else {
+ $filter_o = new Net_LDAP2_Filter();
+ // [TODO]: Do we need to escape at all? what about *-chars user provide and that should remain special?
+ // I think, those prevent escaping! We need to check against PERL Net::LDAP!
+ // $value_arr = Net_LDAP2_Util::escape_filter_value(array($filter_parts[2]));
+ // $value = $value_arr[0];
+ $value = $filter_parts[2];
+ $filter_o->_filter = '('.$filter_parts[0].$filter_parts[1].$value.')';
+ return $filter_o;
+ }
+ }
+ }
+ } else {
+ // ERROR: Filter components must be enclosed in round brackets
+ return PEAR::raiseError("Filter parsing error: invalid filter syntax - filter components must be enclosed in round brackets");
+ }
+ }
+
+ /**
+ * Get the string representation of this filter
+ *
+ * This method runs through all filter objects and creates
+ * the string representation of the filter. If this
+ * filter object is a leaf filter, then it will return
+ * the string representation of this filter.
+ *
+ * @return string|Net_LDAP2_Error
+ */
+ public function asString()
+ {
+ if ($this->isLeaf()) {
+ $return = $this->_filter;
+ } else {
+ $return = '';
+ foreach ($this->_subfilters as $filter) {
+ $return = $return.$filter->asString();
+ }
+ $return = '(' . $this->_match . $return . ')';
+ }
+ return $return;
+ }
+
+ /**
+ * Alias for perl interface as_string()
+ *
+ * @see asString()
+ * @return string|Net_LDAP2_Error
+ */
+ public function as_string()
+ {
+ return $this->asString();
+ }
+
+ /**
+ * Print the text representation of the filter to FH, or the currently selected output handle if FH is not given
+ *
+ * This method is only for compatibility to the perl interface.
+ * However, the original method was called "print" but due to PHP language restrictions,
+ * we can't have a print() method.
+ *
+ * @param resource $FH (optional) A filehandle resource
+ *
+ * @return true|Net_LDAP2_Error
+ */
+ public function printMe($FH = false)
+ {
+ if (!is_resource($FH)) {
+ if (PEAR::isError($FH)) {
+ return $FH;
+ }
+ $filter_str = $this->asString();
+ if (PEAR::isError($filter_str)) {
+ return $filter_str;
+ } else {
+ print($filter_str);
+ }
+ } else {
+ $filter_str = $this->asString();
+ if (PEAR::isError($filter_str)) {
+ return $filter_str;
+ } else {
+ $res = @fwrite($FH, $this->asString());
+ if ($res == false) {
+ return PEAR::raiseError("Unable to write filter string to filehandle \$FH!");
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This can be used to escape a string to provide a valid LDAP-Filter.
+ *
+ * LDAP will only recognise certain characters as the
+ * character istself if they are properly escaped. This is
+ * what this method does.
+ * The method can be called statically, so you can use it outside
+ * for your own purposes (eg for escaping only parts of strings)
+ *
+ * In fact, this is just a shorthand to {@link Net_LDAP2_Util::escape_filter_value()}.
+ * For upward compatibiliy reasons you are strongly encouraged to use the escape
+ * methods provided by the Net_LDAP2_Util class.
+ *
+ * @param string $value Any string who should be escaped
+ *
+ * @static
+ * @return string The string $string, but escaped
+ * @deprecated Do not use this method anymore, instead use Net_LDAP2_Util::escape_filter_value() directly
+ */
+ public static function escape($value)
+ {
+ $return = Net_LDAP2_Util::escape_filter_value(array($value));
+ return $return[0];
+ }
+
+ /**
+ * Is this a container or a leaf filter object?
+ *
+ * @access protected
+ * @return boolean
+ */
+ protected function isLeaf()
+ {
+ if (count($this->_subfilters) > 0) {
+ return false; // Container!
+ } else {
+ return true; // Leaf!
+ }
+ }
+}
+?>
diff --git a/extlib/Net/LDAP2/LDIF.php b/extlib/Net/LDAP2/LDIF.php
new file mode 100644
index 000000000..34f3e75dd
--- /dev/null
+++ b/extlib/Net/LDAP2/LDIF.php
@@ -0,0 +1,922 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_LDIF interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: LDIF.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+require_once 'Net/LDAP2.php';
+require_once 'Net/LDAP2/Entry.php';
+require_once 'Net/LDAP2/Util.php';
+
+/**
+* LDIF capabilitys for Net_LDAP2, closely taken from PERLs Net::LDAP
+*
+* It provides a means to convert between Net_LDAP2_Entry objects and LDAP entries
+* represented in LDIF format files. Reading and writing are supported and may
+* manipulate single entries or lists of entries.
+*
+* Usage example:
+* <code>
+* // Read and parse an ldif-file into Net_LDAP2_Entry objects
+* // and print out the DNs. Store the entries for later use.
+* require 'Net/LDAP2/LDIF.php';
+* $options = array(
+* 'onerror' => 'die'
+* );
+* $entries = array();
+* $ldif = new Net_LDAP2_LDIF('test.ldif', 'r', $options);
+* do {
+* $entry = $ldif->read_entry();
+* $dn = $entry->dn();
+* echo " done building entry: $dn\n";
+* array_push($entries, $entry);
+* } while (!$ldif->eof());
+* $ldif->done();
+*
+*
+* // write those entries to another file
+* $ldif = new Net_LDAP2_LDIF('test.out.ldif', 'w', $options);
+* $ldif->write_entry($entries);
+* $ldif->done();
+* </code>
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP22/
+* @see http://www.ietf.org/rfc/rfc2849.txt
+* @todo Error handling should be PEARified
+* @todo LDAPv3 controls are not implemented yet
+*/
+class Net_LDAP2_LDIF extends PEAR
+{
+ /**
+ * Options
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_options = array('encode' => 'base64',
+ 'onerror' => null,
+ 'change' => 0,
+ 'lowercase' => 0,
+ 'sort' => 0,
+ 'version' => null,
+ 'wrap' => 78,
+ 'raw' => ''
+ );
+
+ /**
+ * Errorcache
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_error = array('error' => null,
+ 'line' => 0
+ );
+
+ /**
+ * Filehandle for read/write
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_FH = null;
+
+ /**
+ * Says, if we opened the filehandle ourselves
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_FH_opened = false;
+
+ /**
+ * Linecounter for input file handle
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_input_line = 0;
+
+ /**
+ * counter for processed entries
+ *
+ * @access protected
+ * @var int
+ */
+ protected $_entrynum = 0;
+
+ /**
+ * Mode we are working in
+ *
+ * Either 'r', 'a' or 'w'
+ *
+ * @access protected
+ * @var string
+ */
+ protected $_mode = false;
+
+ /**
+ * Tells, if the LDIF version string was already written
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $_version_written = false;
+
+ /**
+ * Cache for lines that have build the current entry
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $_lines_cur = array();
+
+ /**
+ * Cache for lines that will build the next entry
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $_lines_next = array();
+
+ /**
+ * Open LDIF file for reading or for writing
+ *
+ * new (FILE):
+ * Open the file read-only. FILE may be the name of a file
+ * or an already open filehandle.
+ * If the file doesn't exist, it will be created if in write mode.
+ *
+ * new (FILE, MODE, OPTIONS):
+ * Open the file with the given MODE (see PHPs fopen()), eg "w" or "a".
+ * FILE may be the name of a file or an already open filehandle.
+ * PERLs Net_LDAP2 "FILE|" mode does not work curently.
+ *
+ * OPTIONS is an associative array and may contain:
+ * encode => 'none' | 'canonical' | 'base64'
+ * Some DN values in LDIF cannot be written verbatim and have to be encoded in some way:
+ * 'none' No encoding.
+ * 'canonical' See "canonical_dn()" in Net::LDAP::Util.
+ * 'base64' Use base64. (default, this differs from the Perl interface.
+ * The perl default is "none"!)
+ *
+ * onerror => 'die' | 'warn' | NULL
+ * Specify what happens when an error is detected.
+ * 'die' Net_LDAP2_LDIF will croak with an appropriate message.
+ * 'warn' Net_LDAP2_LDIF will warn (echo) with an appropriate message.
+ * NULL Net_LDAP2_LDIF will not warn (default), use error().
+ *
+ * change => 1
+ * Write entry changes to the LDIF file instead of the entries itself. I.e. write LDAP
+ * operations acting on the entries to the file instead of the entries contents.
+ * This writes the changes usually carried out by an update() to the LDIF file.
+ *
+ * lowercase => 1
+ * Convert attribute names to lowercase when writing.
+ *
+ * sort => 1
+ * Sort attribute names when writing entries according to the rule:
+ * objectclass first then all other attributes alphabetically sorted by attribute name
+ *
+ * version => '1'
+ * Set the LDIF version to write to the resulting LDIF file.
+ * According to RFC 2849 currently the only legal value for this option is 1.
+ * When this option is set Net_LDAP2_LDIF tries to adhere more strictly to
+ * the LDIF specification in RFC2489 in a few places.
+ * The default is NULL meaning no version information is written to the LDIF file.
+ *
+ * wrap => 78
+ * Number of columns where output line wrapping shall occur.
+ * Default is 78. Setting it to 40 or lower inhibits wrapping.
+ *
+ * raw => REGEX
+ * Use REGEX to denote the names of attributes that are to be
+ * considered binary in search results if writing entries.
+ * Example: raw => "/(?i:^jpegPhoto|;binary)/i"
+ *
+ * @param string|ressource $file Filename or filehandle
+ * @param string $mode Mode to open filename
+ * @param array $options Options like described above
+ */
+ public function __construct($file, $mode = 'r', $options = array())
+ {
+ $this->PEAR('Net_LDAP2_Error'); // default error class
+
+ // First, parse options
+ // todo: maybe implement further checks on possible values
+ foreach ($options as $option => $value) {
+ if (!array_key_exists($option, $this->_options)) {
+ $this->dropError('Net_LDAP2_LDIF error: option '.$option.' not known!');
+ return;
+ } else {
+ $this->_options[$option] = strtolower($value);
+ }
+ }
+
+ // setup LDIF class
+ $this->version($this->_options['version']);
+
+ // setup file mode
+ if (!preg_match('/^[rwa]\+?$/', $mode)) {
+ $this->dropError('Net_LDAP2_LDIF error: file mode '.$mode.' not supported!');
+ } else {
+ $this->_mode = $mode;
+
+ // setup filehandle
+ if (is_resource($file)) {
+ // TODO: checks on mode possible?
+ $this->_FH =& $file;
+ } else {
+ $imode = substr($this->_mode, 0, 1);
+ if ($imode == 'r') {
+ if (!file_exists($file)) {
+ $this->dropError('Unable to open '.$file.' for read: file not found');
+ $this->_mode = false;
+ }
+ if (!is_readable($file)) {
+ $this->dropError('Unable to open '.$file.' for read: permission denied');
+ $this->_mode = false;
+ }
+ }
+
+ if (($imode == 'w' || $imode == 'a')) {
+ if (file_exists($file)) {
+ if (!is_writable($file)) {
+ $this->dropError('Unable to open '.$file.' for write: permission denied');
+ $this->_mode = false;
+ }
+ } else {
+ if (!@touch($file)) {
+ $this->dropError('Unable to create '.$file.' for write: permission denied');
+ $this->_mode = false;
+ }
+ }
+ }
+
+ if ($this->_mode) {
+ $this->_FH = @fopen($file, $this->_mode);
+ if (false === $this->_FH) {
+ // Fallback; should never be reached if tests above are good enough!
+ $this->dropError('Net_LDAP2_LDIF error: Could not open file '.$file);
+ } else {
+ $this->_FH_opened = true;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Read one entry from the file and return it as a Net::LDAP::Entry object.
+ *
+ * @return Net_LDAP2_Entry
+ */
+ public function read_entry()
+ {
+ // read fresh lines, set them as current lines and create the entry
+ $attrs = $this->next_lines(true);
+ if (count($attrs) > 0) {
+ $this->_lines_cur = $attrs;
+ }
+ return $this->current_entry();
+ }
+
+ /**
+ * Returns true when the end of the file is reached.
+ *
+ * @return boolean
+ */
+ public function eof()
+ {
+ return feof($this->_FH);
+ }
+
+ /**
+ * Write the entry or entries to the LDIF file.
+ *
+ * If you want to build an LDIF file containing several entries AND
+ * you want to call write_entry() several times, you must open the filehandle
+ * in append mode ("a"), otherwise you will always get the last entry only.
+ *
+ * @param Net_LDAP2_Entry|array $entries Entry or array of entries
+ *
+ * @return void
+ * @todo implement operations on whole entries (adding a whole entry)
+ */
+ public function write_entry($entries)
+ {
+ if (!is_array($entries)) {
+ $entries = array($entries);
+ }
+
+ foreach ($entries as $entry) {
+ $this->_entrynum++;
+ if (!$entry instanceof Net_LDAP2_Entry) {
+ $this->dropError('Net_LDAP2_LDIF error: entry '.$this->_entrynum.' is not an Net_LDAP2_Entry object');
+ } else {
+ if ($this->_options['change']) {
+ // LDIF change mode
+ // fetch change information from entry
+ $entry_attrs_changes = $entry->getChanges();
+ $num_of_changes = count($entry_attrs_changes['add'])
+ + count($entry_attrs_changes['replace'])
+ + count($entry_attrs_changes['delete']);
+
+ $is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());
+
+ // write version if not done yet
+ // also write DN of entry
+ if ($is_changed) {
+ if (!$this->_version_written) {
+ $this->write_version();
+ }
+ $this->writeDN($entry->currentDN());
+ }
+
+ // process changes
+ // TODO: consider DN add!
+ if ($entry->willBeDeleted()) {
+ $this->writeLine("changetype: delete".PHP_EOL);
+ } elseif ($entry->willBeMoved()) {
+ $this->writeLine("changetype: modrdn".PHP_EOL);
+ $olddn = Net_LDAP2_Util::ldap_explode_dn($entry->currentDN(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
+ $oldrdn = array_shift($olddn);
+ $oldparent = implode(',', $olddn);
+ $newdn = Net_LDAP2_Util::ldap_explode_dn($entry->dn(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
+ $rdn = array_shift($newdn);
+ $parent = implode(',', $newdn);
+ $this->writeLine("newrdn: ".$rdn.PHP_EOL);
+ $this->writeLine("deleteoldrdn: 1".PHP_EOL);
+ if ($parent !== $oldparent) {
+ $this->writeLine("newsuperior: ".$parent.PHP_EOL);
+ }
+ // TODO: What if the entry has attribute changes as well?
+ // I think we should check for that and make a dummy
+ // entry with the changes that is written to the LDIF file
+ } elseif ($num_of_changes > 0) {
+ // write attribute change data
+ $this->writeLine("changetype: modify".PHP_EOL);
+ foreach ($entry_attrs_changes as $changetype => $entry_attrs) {
+ foreach ($entry_attrs as $attr_name => $attr_values) {
+ $this->writeLine("$changetype: $attr_name".PHP_EOL);
+ if ($attr_values !== null) $this->writeAttribute($attr_name, $attr_values, $changetype);
+ $this->writeLine("-".PHP_EOL);
+ }
+ }
+ }
+
+ // finish this entrys data if we had changes
+ if ($is_changed) {
+ $this->finishEntry();
+ }
+ } else {
+ // LDIF-content mode
+ // fetch attributes for further processing
+ $entry_attrs = $entry->getValues();
+
+ // sort and put objectclass-attrs to first position
+ if ($this->_options['sort']) {
+ ksort($entry_attrs);
+ if (array_key_exists('objectclass', $entry_attrs)) {
+ $oc = $entry_attrs['objectclass'];
+ unset($entry_attrs['objectclass']);
+ $entry_attrs = array_merge(array('objectclass' => $oc), $entry_attrs);
+ }
+ }
+
+ // write data
+ if (!$this->_version_written) {
+ $this->write_version();
+ }
+ $this->writeDN($entry->dn());
+ foreach ($entry_attrs as $attr_name => $attr_values) {
+ $this->writeAttribute($attr_name, $attr_values);
+ }
+ $this->finishEntry();
+ }
+ }
+ }
+ }
+
+ /**
+ * Write version to LDIF
+ *
+ * If the object's version is defined, this method allows to explicitely write the version before an entry is written.
+ * If not called explicitely, it gets called automatically when writing the first entry.
+ *
+ * @return void
+ */
+ public function write_version()
+ {
+ $this->_version_written = true;
+ if (!is_null($this->version())) {
+ return $this->writeLine('version: '.$this->version().PHP_EOL, 'Net_LDAP2_LDIF error: unable to write version');
+ }
+ }
+
+ /**
+ * Get or set LDIF version
+ *
+ * If called without arguments it returns the version of the LDIF file or NULL if no version has been set.
+ * If called with an argument it sets the LDIF version to VERSION.
+ * According to RFC 2849 currently the only legal value for VERSION is 1.
+ *
+ * @param int $version (optional) LDIF version to set
+ *
+ * @return int
+ */
+ public function version($version = null)
+ {
+ if ($version !== null) {
+ if ($version != 1) {
+ $this->dropError('Net_LDAP2_LDIF error: illegal LDIF version set');
+ } else {
+ $this->_options['version'] = $version;
+ }
+ }
+ return $this->_options['version'];
+ }
+
+ /**
+ * Returns the file handle the Net_LDAP2_LDIF object reads from or writes to.
+ *
+ * You can, for example, use this to fetch the content of the LDIF file yourself
+ *
+ * @return null|resource
+ */
+ public function &handle()
+ {
+ if (!is_resource($this->_FH)) {
+ $this->dropError('Net_LDAP2_LDIF error: invalid file resource');
+ $null = null;
+ return $null;
+ } else {
+ return $this->_FH;
+ }
+ }
+
+ /**
+ * Clean up
+ *
+ * This method signals that the LDIF object is no longer needed.
+ * You can use this to free up some memory and close the file handle.
+ * The file handle is only closed, if it was opened from Net_LDAP2_LDIF.
+ *
+ * @return void
+ */
+ public function done()
+ {
+ // close FH if we opened it
+ if ($this->_FH_opened) {
+ fclose($this->handle());
+ }
+
+ // free variables
+ foreach (get_object_vars($this) as $name => $value) {
+ unset($this->$name);
+ }
+ }
+
+ /**
+ * Returns last error message if error was found.
+ *
+ * Example:
+ * <code>
+ * $ldif->someAction();
+ * if ($ldif->error()) {
+ * echo "Error: ".$ldif->error()." at input line: ".$ldif->error_lines();
+ * }
+ * </code>
+ *
+ * @param boolean $as_string If set to true, only the message is returned
+ *
+ * @return false|Net_LDAP2_Error
+ */
+ public function error($as_string = false)
+ {
+ if (Net_LDAP2::isError($this->_error['error'])) {
+ return ($as_string)? $this->_error['error']->getMessage() : $this->_error['error'];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns lines that resulted in error.
+ *
+ * Perl returns an array of faulty lines in list context,
+ * but we always just return an int because of PHPs language.
+ *
+ * @return int
+ */
+ public function error_lines()
+ {
+ return $this->_error['line'];
+ }
+
+ /**
+ * Returns the current Net::LDAP::Entry object.
+ *
+ * @return Net_LDAP2_Entry|false
+ */
+ public function current_entry()
+ {
+ return $this->parseLines($this->current_lines());
+ }
+
+ /**
+ * Parse LDIF lines of one entry into an Net_LDAP2_Entry object
+ *
+ * @param array $lines LDIF lines for one entry
+ *
+ * @return Net_LDAP2_Entry|false Net_LDAP2_Entry object for those lines
+ * @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg"
+ */
+ public function parseLines($lines)
+ {
+ // parse lines into an array of attributes and build the entry
+ $attributes = array();
+ $dn = false;
+ foreach ($lines as $line) {
+ if (preg_match('/^(\w+)(:|::|:<)\s(.+)$/', $line, $matches)) {
+ $attr =& $matches[1];
+ $delim =& $matches[2];
+ $data =& $matches[3];
+
+ if ($delim == ':') {
+ // normal data
+ $attributes[$attr][] = $data;
+ } elseif ($delim == '::') {
+ // base64 data
+ $attributes[$attr][] = base64_decode($data);
+ } elseif ($delim == ':<') {
+ // file inclusion
+ // TODO: Is this the job of the LDAP-client or the server?
+ $this->dropError('File inclusions are currently not supported');
+ //$attributes[$attr][] = ...;
+ } else {
+ // since the pattern above, the delimeter cannot be something else.
+ $this->dropError('Net_LDAP2_LDIF parsing error: invalid syntax at parsing entry line: '.$line);
+ continue;
+ }
+
+ if (strtolower($attr) == 'dn') {
+ // DN line detected
+ $dn = $attributes[$attr][0]; // save possibly decoded DN
+ unset($attributes[$attr]); // remove wrongly added "dn: " attribute
+ }
+ } else {
+ // line not in "attr: value" format -> ignore
+ // maybe we should rise an error here, but this should be covered by
+ // next_lines() already. A problem arises, if users try to feed data of
+ // several entries to this method - the resulting entry will
+ // get wrong attributes. However, this is already mentioned in the
+ // methods documentation above.
+ }
+ }
+
+ if (false === $dn) {
+ $this->dropError('Net_LDAP2_LDIF parsing error: unable to detect DN for entry');
+ return false;
+ } else {
+ $newentry = Net_LDAP2_Entry::createFresh($dn, $attributes);
+ return $newentry;
+ }
+ }
+
+ /**
+ * Returns the lines that generated the current Net::LDAP::Entry object.
+ *
+ * Note that this returns an empty array if no lines have been read so far.
+ *
+ * @return array Array of lines
+ */
+ public function current_lines()
+ {
+ return $this->_lines_cur;
+ }
+
+ /**
+ * Returns the lines that will generate the next Net::LDAP::Entry object.
+ *
+ * If you set $force to TRUE then you can iterate over the lines that build
+ * up entries manually. Otherwise, iterating is done using {@link read_entry()}.
+ * Force will move the file pointer forward, thus returning the next entries lines.
+ *
+ * Wrapped lines will be unwrapped. Comments are stripped.
+ *
+ * @param boolean $force Set this to true if you want to iterate over the lines manually
+ *
+ * @return array
+ */
+ public function next_lines($force = false)
+ {
+ // if we already have those lines, just return them, otherwise read
+ if (count($this->_lines_next) == 0 || $force) {
+ $this->_lines_next = array(); // empty in case something was left (if used $force)
+ $entry_done = false;
+ $fh = &$this->handle();
+ $commentmode = false; // if we are in an comment, for wrapping purposes
+ $datalines_read = 0; // how many lines with data we have read
+
+ while (!$entry_done && !$this->eof()) {
+ $this->_input_line++;
+ // Read line. Remove line endings, we want only data;
+ // this is okay since ending spaces should be encoded
+ $data = rtrim(fgets($fh));
+ if ($data === false) {
+ // error only, if EOF not reached after fgets() call
+ if (!$this->eof()) {
+ $this->dropError('Net_LDAP2_LDIF error: error reading from file at input line '.$this->_input_line, $this->_input_line);
+ }
+ break;
+ } else {
+ if (count($this->_lines_next) > 0 && preg_match('/^$/', $data)) {
+ // Entry is finished if we have an empty line after we had data
+ $entry_done = true;
+
+ // Look ahead if the next EOF is nearby. Comments and empty
+ // lines at the file end may cause problems otherwise
+ $current_pos = ftell($fh);
+ $data = fgets($fh);
+ while (!feof($fh)) {
+ if (preg_match('/^\s*$/', $data) || preg_match('/^#/', $data)) {
+ // only empty lines or comments, continue to seek
+ // TODO: Known bug: Wrappings for comments are okay but are treaten as
+ // error, since we do not honor comment mode here.
+ // This should be a very theoretically case, however
+ // i am willing to fix this if really necessary.
+ $this->_input_line++;
+ $current_pos = ftell($fh);
+ $data = fgets($fh);
+ } else {
+ // Data found if non emtpy line and not a comment!!
+ // Rewind to position prior last read and stop lookahead
+ fseek($fh, $current_pos);
+ break;
+ }
+ }
+ // now we have either the file pointer at the beginning of
+ // a new data position or at the end of file causing feof() to return true
+
+ } else {
+ // build lines
+ if (preg_match('/^version:\s(.+)$/', $data, $match)) {
+ // version statement, set version
+ $this->version($match[1]);
+ } elseif (preg_match('/^\w+::?\s.+$/', $data)) {
+ // normal attribute: add line
+ $commentmode = false;
+ $this->_lines_next[] = trim($data);
+ $datalines_read++;
+ } elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
+ // wrapped data: unwrap if not in comment mode
+ if (!$commentmode) {
+ if ($datalines_read == 0) {
+ // first line of entry: wrapped data is illegal
+ $this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
+ } else {
+ $last = array_pop($this->_lines_next);
+ $last = $last.trim($matches[1]);
+ $this->_lines_next[] = $last;
+ $datalines_read++;
+ }
+ }
+ } elseif (preg_match('/^#/', $data)) {
+ // LDIF comments
+ $commentmode = true;
+ } elseif (preg_match('/^\s*$/', $data)) {
+ // empty line but we had no data for this
+ // entry, so just ignore this line
+ $commentmode = false;
+ } else {
+ $this->dropError('Net_LDAP2_LDIF error: invalid syntax at input line '.$this->_input_line, $this->_input_line);
+ continue;
+ }
+
+ }
+ }
+ }
+ }
+ return $this->_lines_next;
+ }
+
+ /**
+ * Convert an attribute and value to LDIF string representation
+ *
+ * It honors correct encoding of values according to RFC 2849.
+ * Line wrapping will occur at the configured maximum but only if
+ * the value is greater than 40 chars.
+ *
+ * @param string $attr_name Name of the attribute
+ * @param string $attr_value Value of the attribute
+ *
+ * @access protected
+ * @return string LDIF string for that attribute and value
+ */
+ protected function convertAttribute($attr_name, $attr_value)
+ {
+ // Handle empty attribute or process
+ if (strlen($attr_value) == 0) {
+ $attr_value = " ";
+ } else {
+ $base64 = false;
+ // ASCII-chars that are NOT safe for the
+ // start and for being inside the value.
+ // These are the int values of those chars.
+ $unsafe_init = array(0, 10, 13, 32, 58, 60);
+ $unsafe = array(0, 10, 13);
+
+ // Test for illegal init char
+ $init_ord = ord(substr($attr_value, 0, 1));
+ if ($init_ord > 127 || in_array($init_ord, $unsafe_init)) {
+ $base64 = true;
+ }
+
+ // Test for illegal content char
+ for ($i = 0; $i < strlen($attr_value); $i++) {
+ $char_ord = ord(substr($attr_value, $i, 1));
+ if ($char_ord > 127 || in_array($char_ord, $unsafe)) {
+ $base64 = true;
+ }
+ }
+
+ // Test for ending space
+ if (substr($attr_value, -1) == ' ') {
+ $base64 = true;
+ }
+
+ // If converting is needed, do it
+ // Either we have some special chars or a matching "raw" regex
+ if ($base64 || ($this->_options['raw'] && preg_match($this->_options['raw'], $attr_name))) {
+ $attr_name .= ':';
+ $attr_value = base64_encode($attr_value);
+ }
+
+ // Lowercase attr names if requested
+ if ($this->_options['lowercase']) $attr_name = strtolower($attr_name);
+
+ // Handle line wrapping
+ if ($this->_options['wrap'] > 40 && strlen($attr_value) > $this->_options['wrap']) {
+ $attr_value = wordwrap($attr_value, $this->_options['wrap'], PHP_EOL." ", true);
+ }
+ }
+
+ return $attr_name.': '.$attr_value;
+ }
+
+ /**
+ * Convert an entries DN to LDIF string representation
+ *
+ * It honors correct encoding of values according to RFC 2849.
+ *
+ * @param string $dn UTF8-Encoded DN
+ *
+ * @access protected
+ * @return string LDIF string for that DN
+ * @todo I am not sure, if the UTF8 stuff is correctly handled right now
+ */
+ protected function convertDN($dn)
+ {
+ $base64 = false;
+ // ASCII-chars that are NOT safe for the
+ // start and for being inside the dn.
+ // These are the int values of those chars.
+ $unsafe_init = array(0, 10, 13, 32, 58, 60);
+ $unsafe = array(0, 10, 13);
+
+ // Test for illegal init char
+ $init_ord = ord(substr($dn, 0, 1));
+ if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) {
+ $base64 = true;
+ }
+
+ // Test for illegal content char
+ for ($i = 0; $i < strlen($dn); $i++) {
+ $char = substr($dn, $i, 1);
+ if (ord($char) >= 127 || in_array($init_ord, $unsafe)) {
+ $base64 = true;
+ }
+ }
+
+ // Test for ending space
+ if (substr($dn, -1) == ' ') {
+ $base64 = true;
+ }
+
+ // if converting is needed, do it
+ return ($base64)? 'dn:: '.base64_encode($dn) : 'dn: '.$dn;
+ }
+
+ /**
+ * Writes an attribute to the filehandle
+ *
+ * @param string $attr_name Name of the attribute
+ * @param string|array $attr_values Single attribute value or array with attribute values
+ *
+ * @access protected
+ * @return void
+ */
+ protected function writeAttribute($attr_name, $attr_values)
+ {
+ // write out attribute content
+ if (!is_array($attr_values)) {
+ $attr_values = array($attr_values);
+ }
+ foreach ($attr_values as $attr_val) {
+ $line = $this->convertAttribute($attr_name, $attr_val).PHP_EOL;
+ $this->writeLine($line, 'Net_LDAP2_LDIF error: unable to write attribute '.$attr_name.' of entry '.$this->_entrynum);
+ }
+ }
+
+ /**
+ * Writes a DN to the filehandle
+ *
+ * @param string $dn DN to write
+ *
+ * @access protected
+ * @return void
+ */
+ protected function writeDN($dn)
+ {
+ // prepare DN
+ if ($this->_options['encode'] == 'base64') {
+ $dn = $this->convertDN($dn).PHP_EOL;
+ } elseif ($this->_options['encode'] == 'canonical') {
+ $dn = Net_LDAP2_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL;
+ } else {
+ $dn = $dn.PHP_EOL;
+ }
+ $this->writeLine($dn, 'Net_LDAP2_LDIF error: unable to write DN of entry '.$this->_entrynum);
+ }
+
+ /**
+ * Finishes an LDIF entry
+ *
+ * @access protected
+ * @return void
+ */
+ protected function finishEntry()
+ {
+ $this->writeLine(PHP_EOL, 'Net_LDAP2_LDIF error: unable to close entry '.$this->_entrynum);
+ }
+
+ /**
+ * Just write an arbitary line to the filehandle
+ *
+ * @param string $line Content to write
+ * @param string $error If error occurs, drop this message
+ *
+ * @access protected
+ * @return true|false
+ */
+ protected function writeLine($line, $error = 'Net_LDAP2_LDIF error: unable to write to filehandle')
+ {
+ if (is_resource($this->handle()) && fwrite($this->handle(), $line, strlen($line)) === false) {
+ $this->dropError($error);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Optionally raises an error and pushes the error on the error cache
+ *
+ * @param string $msg Errortext
+ * @param int $line Line in the LDIF that caused the error
+ *
+ * @access protected
+ * @return void
+ */
+ protected function dropError($msg, $line = null)
+ {
+ $this->_error['error'] = new Net_LDAP2_Error($msg);
+ if ($line !== null) $this->_error['line'] = $line;
+
+ if ($this->_options['onerror'] == 'die') {
+ die($msg.PHP_EOL);
+ } elseif ($this->_options['onerror'] == 'warn') {
+ echo $msg.PHP_EOL;
+ }
+ }
+}
+?>
diff --git a/extlib/Net/LDAP2/RootDSE.php b/extlib/Net/LDAP2/RootDSE.php
new file mode 100644
index 000000000..8dc81fd4f
--- /dev/null
+++ b/extlib/Net/LDAP2/RootDSE.php
@@ -0,0 +1,240 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_RootDSE interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Jan Wagner <wagner@netsols.de>
+* @copyright 2009 Jan Wagner
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: RootDSE.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+
+/**
+* Getting the rootDSE entry of a LDAP server
+*
+* @category Net
+* @package Net_LDAP2
+* @author Jan Wagner <wagner@netsols.de>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP22/
+*/
+class Net_LDAP2_RootDSE extends PEAR
+{
+ /**
+ * @access protected
+ * @var object Net_LDAP2_Entry
+ **/
+ protected $_entry;
+
+ /**
+ * Class constructor
+ *
+ * @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry object of the RootDSE
+ */
+ protected function __construct(&$entry)
+ {
+ $this->_entry = $entry;
+ }
+
+ /**
+ * Fetches a RootDSE object from an LDAP connection
+ *
+ * @param Net_LDAP2 $ldap Directory from which the RootDSE should be fetched
+ * @param array $attrs Array of attributes to search for
+ *
+ * @access static
+ * @return Net_LDAP2_RootDSE|Net_LDAP2_Error
+ */
+ public static function fetch($ldap, $attrs = null)
+ {
+ if (!$ldap instanceof Net_LDAP2) {
+ return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
+ }
+
+ if (is_array($attrs) && count($attrs) > 0 ) {
+ $attributes = $attrs;
+ } else {
+ $attributes = array('vendorName',
+ 'vendorVersion',
+ 'namingContexts',
+ 'altServer',
+ 'supportedExtension',
+ 'supportedControl',
+ 'supportedSASLMechanisms',
+ 'supportedLDAPVersion',
+ 'subschemaSubentry' );
+ }
+ $result = $ldap->search('', '(objectClass=*)', array('attributes' => $attributes, 'scope' => 'base'));
+ if (self::isError($result)) {
+ return $result;
+ }
+ $entry = $result->shiftEntry();
+ if (false === $entry) {
+ return PEAR::raiseError('Could not fetch RootDSE entry');
+ }
+ $ret = new Net_LDAP2_RootDSE($entry);
+ return $ret;
+ }
+
+ /**
+ * Gets the requested attribute value
+ *
+ * Same usuage as {@link Net_LDAP2_Entry::getValue()}
+ *
+ * @param string $attr Attribute name
+ * @param array $options Array of options
+ *
+ * @access public
+ * @return mixed Net_LDAP2_Error object or attribute values
+ * @see Net_LDAP2_Entry::get_value()
+ */
+ public function getValue($attr = '', $options = '')
+ {
+ return $this->_entry->get_value($attr, $options);
+ }
+
+ /**
+ * Alias function of getValue() for perl-ldap interface
+ *
+ * @see getValue()
+ * @return mixed
+ */
+ public function get_value()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'getValue' ), $args);
+ }
+
+ /**
+ * Determines if the extension is supported
+ *
+ * @param array $oids Array of oids to check
+ *
+ * @access public
+ * @return boolean
+ */
+ public function supportedExtension($oids)
+ {
+ return $this->checkAttr($oids, 'supportedExtension');
+ }
+
+ /**
+ * Alias function of supportedExtension() for perl-ldap interface
+ *
+ * @see supportedExtension()
+ * @return boolean
+ */
+ public function supported_extension()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'supportedExtension'), $args);
+ }
+
+ /**
+ * Determines if the version is supported
+ *
+ * @param array $versions Versions to check
+ *
+ * @access public
+ * @return boolean
+ */
+ public function supportedVersion($versions)
+ {
+ return $this->checkAttr($versions, 'supportedLDAPVersion');
+ }
+
+ /**
+ * Alias function of supportedVersion() for perl-ldap interface
+ *
+ * @see supportedVersion()
+ * @return boolean
+ */
+ public function supported_version()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array(&$this, 'supportedVersion'), $args);
+ }
+
+ /**
+ * Determines if the control is supported
+ *
+ * @param array $oids Control oids to check
+ *
+ * @access public
+ * @return boolean
+ */
+ public function supportedControl($oids)
+ {
+ return $this->checkAttr($oids, 'supportedControl');
+ }
+
+ /**
+ * Alias function of supportedControl() for perl-ldap interface
+ *
+ * @see supportedControl()
+ * @return boolean
+ */
+ public function supported_control()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array(&$this, 'supportedControl' ), $args);
+ }
+
+ /**
+ * Determines if the sasl mechanism is supported
+ *
+ * @param array $mechlist SASL mechanisms to check
+ *
+ * @access public
+ * @return boolean
+ */
+ public function supportedSASLMechanism($mechlist)
+ {
+ return $this->checkAttr($mechlist, 'supportedSASLMechanisms');
+ }
+
+ /**
+ * Alias function of supportedSASLMechanism() for perl-ldap interface
+ *
+ * @see supportedSASLMechanism()
+ * @return boolean
+ */
+ public function supported_sasl_mechanism()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array(&$this, 'supportedSASLMechanism'), $args);
+ }
+
+ /**
+ * Checks for existance of value in attribute
+ *
+ * @param array $values values to check
+ * @param string $attr attribute name
+ *
+ * @access protected
+ * @return boolean
+ */
+ protected function checkAttr($values, $attr)
+ {
+ if (!is_array($values)) $values = array($values);
+
+ foreach ($values as $value) {
+ if (!@in_array($value, $this->get_value($attr, 'all'))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+?>
diff --git a/extlib/Net/LDAP2/Schema.php b/extlib/Net/LDAP2/Schema.php
new file mode 100644
index 000000000..b590eabc5
--- /dev/null
+++ b/extlib/Net/LDAP2/Schema.php
@@ -0,0 +1,516 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_Schema interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Jan Wagner <wagner@netsols.de>
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Jan Wagner, Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: Schema.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+* @todo see the comment at the end of the file
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+
+/**
+* Syntax definitions
+*
+* Please don't forget to add binary attributes to isBinary() below
+* to support proper value fetching from Net_LDAP2_Entry
+*/
+define('NET_LDAP2_SYNTAX_BOOLEAN', '1.3.6.1.4.1.1466.115.121.1.7');
+define('NET_LDAP2_SYNTAX_DIRECTORY_STRING', '1.3.6.1.4.1.1466.115.121.1.15');
+define('NET_LDAP2_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');
+define('NET_LDAP2_SYNTAX_INTEGER', '1.3.6.1.4.1.1466.115.121.1.27');
+define('NET_LDAP2_SYNTAX_JPEG', '1.3.6.1.4.1.1466.115.121.1.28');
+define('NET_LDAP2_SYNTAX_NUMERIC_STRING', '1.3.6.1.4.1.1466.115.121.1.36');
+define('NET_LDAP2_SYNTAX_OID', '1.3.6.1.4.1.1466.115.121.1.38');
+define('NET_LDAP2_SYNTAX_OCTET_STRING', '1.3.6.1.4.1.1466.115.121.1.40');
+
+/**
+* Load an LDAP Schema and provide information
+*
+* This class takes a Subschema entry, parses this information
+* and makes it available in an array. Most of the code has been
+* inspired by perl-ldap( http://perl-ldap.sourceforge.net).
+* You will find portions of their implementation in here.
+*
+* @category Net
+* @package Net_LDAP2
+* @author Jan Wagner <wagner@netsols.de>
+* @author Benedikt Hallinger <beni@php.net>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP22/
+*/
+class Net_LDAP2_Schema extends PEAR
+{
+ /**
+ * Map of entry types to ldap attributes of subschema entry
+ *
+ * @access public
+ * @var array
+ */
+ public $types = array(
+ 'attribute' => 'attributeTypes',
+ 'ditcontentrule' => 'dITContentRules',
+ 'ditstructurerule' => 'dITStructureRules',
+ 'matchingrule' => 'matchingRules',
+ 'matchingruleuse' => 'matchingRuleUse',
+ 'nameform' => 'nameForms',
+ 'objectclass' => 'objectClasses',
+ 'syntax' => 'ldapSyntaxes'
+ );
+
+ /**
+ * Array of entries belonging to this type
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_attributeTypes = array();
+ protected $_matchingRules = array();
+ protected $_matchingRuleUse = array();
+ protected $_ldapSyntaxes = array();
+ protected $_objectClasses = array();
+ protected $_dITContentRules = array();
+ protected $_dITStructureRules = array();
+ protected $_nameForms = array();
+
+
+ /**
+ * hash of all fetched oids
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_oids = array();
+
+ /**
+ * Tells if the schema is initialized
+ *
+ * @access protected
+ * @var boolean
+ * @see parse(), get()
+ */
+ protected $_initialized = false;
+
+
+ /**
+ * Constructor of the class
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ $this->PEAR('Net_LDAP2_Error'); // default error class
+ }
+
+ /**
+ * Fetch the Schema from an LDAP connection
+ *
+ * @param Net_LDAP2 $ldap LDAP connection
+ * @param string $dn (optional) Subschema entry dn
+ *
+ * @access public
+ * @return Net_LDAP2_Schema|NET_LDAP2_Error
+ */
+ public function fetch($ldap, $dn = null)
+ {
+ if (!$ldap instanceof Net_LDAP2) {
+ return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
+ }
+
+ $schema_o = new Net_LDAP2_Schema();
+
+ if (is_null($dn)) {
+ // get the subschema entry via root dse
+ $dse = $ldap->rootDSE(array('subschemaSubentry'));
+ if (false == Net_LDAP2::isError($dse)) {
+ $base = $dse->getValue('subschemaSubentry', 'single');
+ if (!Net_LDAP2::isError($base)) {
+ $dn = $base;
+ }
+ }
+ }
+
+ // Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly
+ // call this entry subSchemaSubentry instead of subschemaSubentry.
+ // Note the correct case/spelling as per RFC 2251.
+ if (is_null($dn)) {
+ // get the subschema entry via root dse
+ $dse = $ldap->rootDSE(array('subSchemaSubentry'));
+ if (false == Net_LDAP2::isError($dse)) {
+ $base = $dse->getValue('subSchemaSubentry', 'single');
+ if (!Net_LDAP2::isError($base)) {
+ $dn = $base;
+ }
+ }
+ }
+
+ // Final fallback case where there is no subschemaSubentry attribute
+ // in the root DSE (this is a bug for an LDAP v3 server so report this
+ // to your LDAP vendor if you get this far).
+ if (is_null($dn)) {
+ $dn = 'cn=Subschema';
+ }
+
+ // fetch the subschema entry
+ $result = $ldap->search($dn, '(objectClass=*)',
+ array('attributes' => array_values($schema_o->types),
+ 'scope' => 'base'));
+ if (Net_LDAP2::isError($result)) {
+ return $result;
+ }
+
+ $entry = $result->shiftEntry();
+ if (!$entry instanceof Net_LDAP2_Entry) {
+ return PEAR::raiseError('Could not fetch Subschema entry');
+ }
+
+ $schema_o->parse($entry);
+ return $schema_o;
+ }
+
+ /**
+ * Return a hash of entries for the given type
+ *
+ * Returns a hash of entry for th givene type. Types may be:
+ * objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
+ * matchingruleuses, nameforms, syntaxes
+ *
+ * @param string $type Type to fetch
+ *
+ * @access public
+ * @return array|Net_LDAP2_Error Array or Net_LDAP2_Error
+ */
+ public function &getAll($type)
+ {
+ $map = array('objectclasses' => &$this->_objectClasses,
+ 'attributes' => &$this->_attributeTypes,
+ 'ditcontentrules' => &$this->_dITContentRules,
+ 'ditstructurerules' => &$this->_dITStructureRules,
+ 'matchingrules' => &$this->_matchingRules,
+ 'matchingruleuses' => &$this->_matchingRuleUse,
+ 'nameforms' => &$this->_nameForms,
+ 'syntaxes' => &$this->_ldapSyntaxes );
+
+ $key = strtolower($type);
+ $ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));
+ return $ret;
+ }
+
+ /**
+ * Return a specific entry
+ *
+ * @param string $type Type of name
+ * @param string $name Name or OID to fetch
+ *
+ * @access public
+ * @return mixed Entry or Net_LDAP2_Error
+ */
+ public function &get($type, $name)
+ {
+ if ($this->_initialized) {
+ $type = strtolower($type);
+ if (false == key_exists($type, $this->types)) {
+ return PEAR::raiseError("No such type $type");
+ }
+
+ $name = strtolower($name);
+ $type_var = &$this->{'_' . $this->types[$type]};
+
+ if (key_exists($name, $type_var)) {
+ return $type_var[$name];
+ } elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {
+ return $this->_oids[$name];
+ } else {
+ return PEAR::raiseError("Could not find $type $name");
+ }
+ } else {
+ $return = null;
+ return $return;
+ }
+ }
+
+
+ /**
+ * Fetches attributes that MAY be present in the given objectclass
+ *
+ * @param string $oc Name or OID of objectclass
+ *
+ * @access public
+ * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
+ */
+ public function may($oc)
+ {
+ return $this->_getAttr($oc, 'may');
+ }
+
+ /**
+ * Fetches attributes that MUST be present in the given objectclass
+ *
+ * @param string $oc Name or OID of objectclass
+ *
+ * @access public
+ * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
+ */
+ public function must($oc)
+ {
+ return $this->_getAttr($oc, 'must');
+ }
+
+ /**
+ * Fetches the given attribute from the given objectclass
+ *
+ * @param string $oc Name or OID of objectclass
+ * @param string $attr Name of attribute to fetch
+ *
+ * @access protected
+ * @return array|Net_LDAP2_Error The attribute or Net_LDAP2_Error
+ */
+ protected function _getAttr($oc, $attr)
+ {
+ $oc = strtolower($oc);
+ if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {
+ return $this->_objectClasses[$oc][$attr];
+ } elseif (key_exists($oc, $this->_oids) &&
+ $this->_oids[$oc]['type'] == 'objectclass' &&
+ key_exists($attr, $this->_oids[$oc])) {
+ return $this->_oids[$oc][$attr];
+ } else {
+ return PEAR::raiseError("Could not find $attr attributes for $oc ");
+ }
+ }
+
+ /**
+ * Returns the name(s) of the immediate superclass(es)
+ *
+ * @param string $oc Name or OID of objectclass
+ *
+ * @access public
+ * @return array|Net_LDAP2_Error Array of names or Net_LDAP2_Error
+ */
+ public function superclass($oc)
+ {
+ $o = $this->get('objectclass', $oc);
+ if (Net_LDAP2::isError($o)) {
+ return $o;
+ }
+ return (key_exists('sup', $o) ? $o['sup'] : array());
+ }
+
+ /**
+ * Parses the schema of the given Subschema entry
+ *
+ * @param Net_LDAP2_Entry &$entry Subschema entry
+ *
+ * @access public
+ * @return void
+ */
+ public function parse(&$entry)
+ {
+ foreach ($this->types as $type => $attr) {
+ // initialize map type to entry
+ $type_var = '_' . $attr;
+ $this->{$type_var} = array();
+
+ // get values for this type
+ if ($entry->exists($attr)) {
+ $values = $entry->getValue($attr);
+ if (is_array($values)) {
+ foreach ($values as $value) {
+
+ unset($schema_entry); // this was a real mess without it
+
+ // get the schema entry
+ $schema_entry = $this->_parse_entry($value);
+
+ // set the type
+ $schema_entry['type'] = $type;
+
+ // save a ref in $_oids
+ $this->_oids[$schema_entry['oid']] = &$schema_entry;
+
+ // save refs for all names in type map
+ $names = $schema_entry['aliases'];
+ array_push($names, $schema_entry['name']);
+ foreach ($names as $name) {
+ $this->{$type_var}[strtolower($name)] = &$schema_entry;
+ }
+ }
+ }
+ }
+ }
+ $this->_initialized = true;
+ }
+
+ /**
+ * Parses an attribute value into a schema entry
+ *
+ * @param string $value Attribute value
+ *
+ * @access protected
+ * @return array|false Schema entry array or false
+ */
+ protected function &_parse_entry($value)
+ {
+ // tokens that have no value associated
+ $noValue = array('single-value',
+ 'obsolete',
+ 'collective',
+ 'no-user-modification',
+ 'abstract',
+ 'structural',
+ 'auxiliary');
+
+ // tokens that can have multiple values
+ $multiValue = array('must', 'may', 'sup');
+
+ $schema_entry = array('aliases' => array()); // initilization
+
+ $tokens = $this->_tokenize($value); // get an array of tokens
+
+ // remove surrounding brackets
+ if ($tokens[0] == '(') array_shift($tokens);
+ if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-(
+
+ $schema_entry['oid'] = array_shift($tokens); // first token is the oid
+
+ // cycle over the tokens until none are left
+ while (count($tokens) > 0) {
+ $token = strtolower(array_shift($tokens));
+ if (in_array($token, $noValue)) {
+ $schema_entry[$token] = 1; // single value token
+ } else {
+ // this one follows a string or a list if it is multivalued
+ if (($schema_entry[$token] = array_shift($tokens)) == '(') {
+ // this creates the list of values and cycles through the tokens
+ // until the end of the list is reached ')'
+ $schema_entry[$token] = array();
+ while ($tmp = array_shift($tokens)) {
+ if ($tmp == ')') break;
+ if ($tmp != '$') array_push($schema_entry[$token], $tmp);
+ }
+ }
+ // create a array if the value should be multivalued but was not
+ if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
+ $schema_entry[$token] = array($schema_entry[$token]);
+ }
+ }
+ }
+ // get max length from syntax
+ if (key_exists('syntax', $schema_entry)) {
+ if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
+ $schema_entry['max_length'] = $matches[1];
+ }
+ }
+ // force a name
+ if (empty($schema_entry['name'])) {
+ $schema_entry['name'] = $schema_entry['oid'];
+ }
+ // make one name the default and put the other ones into aliases
+ if (is_array($schema_entry['name'])) {
+ $aliases = $schema_entry['name'];
+ $schema_entry['name'] = array_shift($aliases);
+ $schema_entry['aliases'] = $aliases;
+ }
+ return $schema_entry;
+ }
+
+ /**
+ * Tokenizes the given value into an array of tokens
+ *
+ * @param string $value String to parse
+ *
+ * @access protected
+ * @return array Array of tokens
+ */
+ protected function _tokenize($value)
+ {
+ $tokens = array(); // array of tokens
+ $matches = array(); // matches[0] full pattern match, [1,2,3] subpatterns
+
+ // this one is taken from perl-ldap, modified for php
+ $pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";
+
+ /**
+ * This one matches one big pattern wherin only one of the three subpatterns matched
+ * We are interested in the subpatterns that matched. If it matched its value will be
+ * non-empty and so it is a token. Tokens may be round brackets, a string, or a string
+ * enclosed by '
+ */
+ preg_match_all($pattern, $value, $matches);
+
+ for ($i = 0; $i < count($matches[0]); $i++) { // number of tokens (full pattern match)
+ for ($j = 1; $j < 4; $j++) { // each subpattern
+ if (null != trim($matches[$j][$i])) { // pattern match in this subpattern
+ $tokens[$i] = trim($matches[$j][$i]); // this is the token
+ }
+ }
+ }
+ return $tokens;
+ }
+
+ /**
+ * Returns wether a attribute syntax is binary or not
+ *
+ * This method gets used by Net_LDAP2_Entry to decide which
+ * PHP function needs to be used to fetch the value in the
+ * proper format (e.g. binary or string)
+ *
+ * @param string $attribute The name of the attribute (eg.: 'sn')
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isBinary($attribute)
+ {
+ $return = false; // default to false
+
+ // This list contains all syntax that should be treaten as
+ // containing binary values
+ // The Syntax Definitons go into constants at the top of this page
+ $syntax_binary = array(
+ NET_LDAP2_SYNTAX_OCTET_STRING,
+ NET_LDAP2_SYNTAX_JPEG
+ );
+
+ // Check Syntax
+ $attr_s = $this->get('attribute', $attribute);
+ if (Net_LDAP2::isError($attr_s)) {
+ // Attribute not found in schema
+ $return = false; // consider attr not binary
+ } elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {
+ // Syntax is defined as binary in schema
+ $return = true;
+ } else {
+ // Syntax not defined as binary, or not found
+ // if attribute is a subtype, check superior attribute syntaxes
+ if (isset($attr_s['sup'])) {
+ foreach ($attr_s['sup'] as $superattr) {
+ $return = $this->isBinary($superattr);
+ if ($return) {
+ break; // stop checking parents since we are binary
+ }
+ }
+ }
+ }
+
+ return $return;
+ }
+
+ // [TODO] add method that allows us to see to which objectclasses a certain attribute belongs to
+ // it should return the result structured, e.g. sorted in "may" and "must". Optionally it should
+ // be able to return it just "flat", e.g. array_merge()d.
+ // We could use get_all() to achieve this easily, i think
+}
+?>
diff --git a/extlib/Net/LDAP2/SchemaCache.interface.php b/extlib/Net/LDAP2/SchemaCache.interface.php
new file mode 100644
index 000000000..e0c3094c4
--- /dev/null
+++ b/extlib/Net/LDAP2/SchemaCache.interface.php
@@ -0,0 +1,59 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_SchemaCache interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: SchemaCache.interface.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Interface describing a custom schema cache object
+*
+* To implement a custom schema cache, one must implement this interface and
+* pass the instanciated object to Net_LDAP2s registerSchemaCache() method.
+*/
+interface Net_LDAP2_SchemaCache
+{
+ /**
+ * Return the schema object from the cache
+ *
+ * Net_LDAP2 will consider anything returned invalid, except
+ * a valid Net_LDAP2_Schema object.
+ * In case you return a Net_LDAP2_Error, this error will be routed
+ * to the return of the $ldap->schema() call.
+ * If you return something else, Net_LDAP2 will
+ * fetch a fresh Schema object from the LDAP server.
+ *
+ * You may want to implement a cache aging mechanism here too.
+ *
+ * @return Net_LDAP2_Schema|Net_LDAP2_Error|false
+ */
+ public function loadSchema();
+
+ /**
+ * Store a schema object in the cache
+ *
+ * This method will be called, if Net_LDAP2 has fetched a fresh
+ * schema object from LDAP and wants to init or refresh the cache.
+ *
+ * In case of errors you may return a Net_LDAP2_Error which will
+ * be routet to the client.
+ * Note that doing this prevents, that the schema object fetched from LDAP
+ * will be given back to the client, so only return errors if storing
+ * of the cache is something crucial (e.g. for doing something else with it).
+ * Normaly you dont want to give back errors in which case Net_LDAP2 needs to
+ * fetch the schema once per script run and instead use the error
+ * returned from loadSchema().
+ *
+ * @return true|Net_LDAP2_Error
+ */
+ public function storeSchema($schema);
+}
diff --git a/extlib/Net/LDAP2/Search.php b/extlib/Net/LDAP2/Search.php
new file mode 100644
index 000000000..de4fde122
--- /dev/null
+++ b/extlib/Net/LDAP2/Search.php
@@ -0,0 +1,614 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_Search interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Tarjej Huse, Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: Search.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+
+/**
+* Result set of an LDAP search
+*
+* @category Net
+* @package Net_LDAP2
+* @author Tarjej Huse <tarjei@bergfald.no>
+* @author Benedikt Hallinger <beni@php.net>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP22/
+*/
+class Net_LDAP2_Search extends PEAR implements Iterator
+{
+ /**
+ * Search result identifier
+ *
+ * @access protected
+ * @var resource
+ */
+ protected $_search;
+
+ /**
+ * LDAP resource link
+ *
+ * @access protected
+ * @var resource
+ */
+ protected $_link;
+
+ /**
+ * Net_LDAP2 object
+ *
+ * A reference of the Net_LDAP2 object for passing to Net_LDAP2_Entry
+ *
+ * @access protected
+ * @var object Net_LDAP2
+ */
+ protected $_ldap;
+
+ /**
+ * Result entry identifier
+ *
+ * @access protected
+ * @var resource
+ */
+ protected $_entry = null;
+
+ /**
+ * The errorcode the search got
+ *
+ * Some errorcodes might be of interest, but might not be best handled as errors.
+ * examples: 4 - LDAP_SIZELIMIT_EXCEEDED - indicates a huge search.
+ * Incomplete results are returned. If you just want to check if there's anything in the search.
+ * than this is a point to handle.
+ * 32 - no such object - search here returns a count of 0.
+ *
+ * @access protected
+ * @var int
+ */
+ protected $_errorCode = 0; // if not set - sucess!
+
+ /**
+ * Cache for all entries already fetched from iterator interface
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_iteratorCache = array();
+
+ /**
+ * What attributes we searched for
+ *
+ * The $attributes array contains the names of the searched attributes and gets
+ * passed from $Net_LDAP2->search() so the Net_LDAP2_Search object can tell
+ * what attributes was searched for ({@link searchedAttrs())
+ *
+ * This variable gets set from the constructor and returned
+ * from {@link searchedAttrs()}
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_searchedAttrs = array();
+
+ /**
+ * Cache variable for storing entries fetched internally
+ *
+ * This currently is only used by {@link pop_entry()}
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_entry_cache = false;
+
+ /**
+ * Constructor
+ *
+ * @param resource &$search Search result identifier
+ * @param Net_LDAP2|resource &$ldap Net_LDAP2 object or just a LDAP-Link resource
+ * @param array $attributes (optional) Array with searched attribute names. (see {@link $_searchedAttrs})
+ *
+ * @access public
+ */
+ public function __construct(&$search, &$ldap, $attributes = array())
+ {
+ $this->PEAR('Net_LDAP2_Error');
+
+ $this->setSearch($search);
+
+ if ($ldap instanceof Net_LDAP2) {
+ $this->_ldap =& $ldap;
+ $this->setLink($this->_ldap->getLink());
+ } else {
+ $this->setLink($ldap);
+ }
+
+ $this->_errorCode = @ldap_errno($this->_link);
+
+ if (is_array($attributes) && !empty($attributes)) {
+ $this->_searchedAttrs = $attributes;
+ }
+ }
+
+ /**
+ * Returns an array of entry objects
+ *
+ * @return array Array of entry objects.
+ */
+ public function entries()
+ {
+ $entries = array();
+
+ while ($entry = $this->shiftEntry()) {
+ $entries[] = $entry;
+ }
+
+ return $entries;
+ }
+
+ /**
+ * Get the next entry in the searchresult.
+ *
+ * This will return a valid Net_LDAP2_Entry object or false, so
+ * you can use this method to easily iterate over the entries inside
+ * a while loop.
+ *
+ * @return Net_LDAP2_Entry|false Reference to Net_LDAP2_Entry object or false
+ */
+ public function &shiftEntry()
+ {
+ if ($this->count() == 0 ) {
+ $false = false;
+ return $false;
+ }
+
+ if (is_null($this->_entry)) {
+ $this->_entry = @ldap_first_entry($this->_link, $this->_search);
+ $entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
+ if ($entry instanceof Net_LDAP2_Error) $entry = false;
+ } else {
+ if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) {
+ $false = false;
+ return $false;
+ }
+ $entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
+ if ($entry instanceof Net_LDAP2_Error) $entry = false;
+ }
+ return $entry;
+ }
+
+ /**
+ * Alias function of shiftEntry() for perl-ldap interface
+ *
+ * @see shiftEntry()
+ * @return Net_LDAP2_Entry|false
+ */
+ public function shift_entry()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'shiftEntry' ), $args);
+ }
+
+ /**
+ * Retrieve the next entry in the searchresult, but starting from last entry
+ *
+ * This is the opposite to {@link shiftEntry()} and is also very useful
+ * to be used inside a while loop.
+ *
+ * @return Net_LDAP2_Entry|false
+ */
+ public function popEntry()
+ {
+ if (false === $this->_entry_cache) {
+ // fetch entries into cache if not done so far
+ $this->_entry_cache = $this->entries();
+ }
+
+ $return = array_pop($this->_entry_cache);
+ return (null === $return)? false : $return;
+ }
+
+ /**
+ * Alias function of popEntry() for perl-ldap interface
+ *
+ * @see popEntry()
+ * @return Net_LDAP2_Entry|false
+ */
+ public function pop_entry()
+ {
+ $args = func_get_args();
+ return call_user_func_array(array( &$this, 'popEntry' ), $args);
+ }
+
+ /**
+ * Return entries sorted as array
+ *
+ * This returns a array with sorted entries and the values.
+ * Sorting is done with PHPs {@link array_multisort()}.
+ * This method relies on {@link as_struct()} to fetch the raw data of the entries.
+ *
+ * Please note that attribute names are case sensitive!
+ *
+ * Usage example:
+ * <code>
+ * // to sort entries first by location, then by surename, but descending:
+ * $entries = $search->sorted_as_struct(array('locality','sn'), SORT_DESC);
+ * </code>
+ *
+ * @param array $attrs Array of attribute names to sort; order from left to right.
+ * @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC
+ *
+ * @return array|Net_LDAP2_Error Array with sorted entries or error
+ * @todo what about server side sorting as specified in http://www.ietf.org/rfc/rfc2891.txt?
+ */
+ public function sorted_as_struct($attrs = array('cn'), $order = SORT_ASC)
+ {
+ /*
+ * Old Code, suitable and fast for single valued sorting
+ * This code should be used if we know that single valued sorting is desired,
+ * but we need some method to get that knowledge...
+ */
+ /*
+ $attrs = array_reverse($attrs);
+ foreach ($attrs as $attribute) {
+ if (!ldap_sort($this->_link, $this->_search, $attribute)){
+ $this->raiseError("Sorting failed for Attribute " . $attribute);
+ }
+ }
+
+ $results = ldap_get_entries($this->_link, $this->_search);
+
+ unset($results['count']); //for tidier output
+ if ($order) {
+ return array_reverse($results);
+ } else {
+ return $results;
+ }*/
+
+ /*
+ * New code: complete "client side" sorting
+ */
+ // first some parameterchecks
+ if (!is_array($attrs)) {
+ return PEAR::raiseError("Sorting failed: Parameterlist must be an array!");
+ }
+ if ($order != SORT_ASC && $order != SORT_DESC) {
+ return PEAR::raiseError("Sorting failed: sorting direction not understood! (neither constant SORT_ASC nor SORT_DESC)");
+ }
+
+ // fetch the entries data
+ $entries = $this->as_struct();
+
+ // now sort each entries attribute values
+ // this is neccessary because later we can only sort by one value,
+ // so we need the highest or lowest attribute now, depending on the
+ // selected ordering for that specific attribute
+ foreach ($entries as $dn => $entry) {
+ foreach ($entry as $attr_name => $attr_values) {
+ sort($entries[$dn][$attr_name]);
+ if ($order == SORT_DESC) {
+ array_reverse($entries[$dn][$attr_name]);
+ }
+ }
+ }
+
+ // reformat entrys array for later use with array_multisort()
+ $to_sort = array(); // <- will be a numeric array similar to ldap_get_entries
+ foreach ($entries as $dn => $entry_attr) {
+ $row = array();
+ $row['dn'] = $dn;
+ foreach ($entry_attr as $attr_name => $attr_values) {
+ $row[$attr_name] = $attr_values;
+ }
+ $to_sort[] = $row;
+ }
+
+ // Build columns for array_multisort()
+ // each requested attribute is one row
+ $columns = array();
+ foreach ($attrs as $attr_name) {
+ foreach ($to_sort as $key => $row) {
+ $columns[$attr_name][$key] =& $to_sort[$key][$attr_name][0];
+ }
+ }
+
+ // sort the colums with array_multisort, if there is something
+ // to sort and if we have requested sort columns
+ if (!empty($to_sort) && !empty($columns)) {
+ $sort_params = '';
+ foreach ($attrs as $attr_name) {
+ $sort_params .= '$columns[\''.$attr_name.'\'], '.$order.', ';
+ }
+ eval("array_multisort($sort_params \$to_sort);"); // perform sorting
+ }
+
+ return $to_sort;
+ }
+
+ /**
+ * Return entries sorted as objects
+ *
+ * This returns a array with sorted Net_LDAP2_Entry objects.
+ * The sorting is actually done with {@link sorted_as_struct()}.
+ *
+ * Please note that attribute names are case sensitive!
+ * Also note, that it is (depending on server capabilitys) possible to let
+ * the server sort your results. This happens through search controls
+ * and is described in detail at {@link http://www.ietf.org/rfc/rfc2891.txt}
+ *
+ * Usage example:
+ * <code>
+ * // to sort entries first by location, then by surename, but descending:
+ * $entries = $search->sorted(array('locality','sn'), SORT_DESC);
+ * </code>
+ *
+ * @param array $attrs Array of sort attributes to sort; order from left to right.
+ * @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC
+ *
+ * @return array|Net_LDAP2_Error Array with sorted Net_LDAP2_Entries or error
+ * @todo Entry object construction could be faster. Maybe we could use one of the factorys instead of fetching the entry again
+ */
+ public function sorted($attrs = array('cn'), $order = SORT_ASC)
+ {
+ $return = array();
+ $sorted = $this->sorted_as_struct($attrs, $order);
+ if (PEAR::isError($sorted)) {
+ return $sorted;
+ }
+ foreach ($sorted as $key => $row) {
+ $entry = $this->_ldap->getEntry($row['dn'], $this->searchedAttrs());
+ if (!PEAR::isError($entry)) {
+ array_push($return, $entry);
+ } else {
+ return $entry;
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Return entries as array
+ *
+ * This method returns the entries and the selected attributes values as
+ * array.
+ * The first array level contains all found entries where the keys are the
+ * DNs of the entries. The second level arrays contian the entries attributes
+ * such that the keys is the lowercased name of the attribute and the values
+ * are stored in another indexed array. Note that the attribute values are stored
+ * in an array even if there is no or just one value.
+ *
+ * The array has the following structure:
+ * <code>
+ * $return = array(
+ * 'cn=foo,dc=example,dc=com' => array(
+ * 'sn' => array('foo'),
+ * 'multival' => array('val1', 'val2', 'valN')
+ * )
+ * 'cn=bar,dc=example,dc=com' => array(
+ * 'sn' => array('bar'),
+ * 'multival' => array('val1', 'valN')
+ * )
+ * )
+ * </code>
+ *
+ * @return array associative result array as described above
+ */
+ public function as_struct()
+ {
+ $return = array();
+ $entries = $this->entries();
+ foreach ($entries as $entry) {
+ $attrs = array();
+ $entry_attributes = $entry->attributes();
+ foreach ($entry_attributes as $attr_name) {
+ $attr_values = $entry->getValue($attr_name, 'all');
+ if (!is_array($attr_values)) {
+ $attr_values = array($attr_values);
+ }
+ $attrs[$attr_name] = $attr_values;
+ }
+ $return[$entry->dn()] = $attrs;
+ }
+ return $return;
+ }
+
+ /**
+ * Set the search objects resource link
+ *
+ * @param resource &$search Search result identifier
+ *
+ * @access public
+ * @return void
+ */
+ public function setSearch(&$search)
+ {
+ $this->_search = $search;
+ }
+
+ /**
+ * Set the ldap ressource link
+ *
+ * @param resource &$link Link identifier
+ *
+ * @access public
+ * @return void
+ */
+ public function setLink(&$link)
+ {
+ $this->_link = $link;
+ }
+
+ /**
+ * Returns the number of entries in the searchresult
+ *
+ * @return int Number of entries in search.
+ */
+ public function count()
+ {
+ // this catches the situation where OL returned errno 32 = no such object!
+ if (!$this->_search) {
+ return 0;
+ }
+ return @ldap_count_entries($this->_link, $this->_search);
+ }
+
+ /**
+ * Get the errorcode the object got in its search.
+ *
+ * @return int The ldap error number.
+ */
+ public function getErrorCode()
+ {
+ return $this->_errorCode;
+ }
+
+ /**
+ * Destructor
+ *
+ * @access protected
+ */
+ public function _Net_LDAP2_Search()
+ {
+ @ldap_free_result($this->_search);
+ }
+
+ /**
+ * Closes search result
+ *
+ * @return void
+ */
+ public function done()
+ {
+ $this->_Net_LDAP2_Search();
+ }
+
+ /**
+ * Return the attribute names this search selected
+ *
+ * @return array
+ * @see $_searchedAttrs
+ * @access protected
+ */
+ protected function searchedAttrs()
+ {
+ return $this->_searchedAttrs;
+ }
+
+ /**
+ * Tells if this search exceeds a sizelimit
+ *
+ * @return boolean
+ */
+ public function sizeLimitExceeded()
+ {
+ return ($this->getErrorCode() == 4);
+ }
+
+
+ /*
+ * SPL Iterator interface methods.
+ * This interface allows to use Net_LDAP2_Search
+ * objects directly inside a foreach loop!
+ */
+ /**
+ * SPL Iterator interface: Return the current element.
+ *
+ * The SPL Iterator interface allows you to fetch entries inside
+ * a foreach() loop: <code>foreach ($search as $dn => $entry) { ...</code>
+ *
+ * Of course, you may call {@link current()}, {@link key()}, {@link next()},
+ * {@link rewind()} and {@link valid()} yourself.
+ *
+ * If the search throwed an error, it returns false.
+ * False is also returned, if the end is reached
+ * In case no call to next() was made, we will issue one,
+ * thus returning the first entry.
+ *
+ * @return Net_LDAP2_Entry|false
+ */
+ public function current()
+ {
+ if (count($this->_iteratorCache) == 0) {
+ $this->next();
+ reset($this->_iteratorCache);
+ }
+ $entry = current($this->_iteratorCache);
+ return ($entry instanceof Net_LDAP2_Entry)? $entry : false;
+ }
+
+ /**
+ * SPL Iterator interface: Return the identifying key (DN) of the current entry.
+ *
+ * @see current()
+ * @return string|false DN of the current entry; false in case no entry is returned by current()
+ */
+ public function key()
+ {
+ $entry = $this->current();
+ return ($entry instanceof Net_LDAP2_Entry)? $entry->dn() :false;
+ }
+
+ /**
+ * SPL Iterator interface: Move forward to next entry.
+ *
+ * After a call to {@link next()}, {@link current()} will return
+ * the next entry in the result set.
+ *
+ * @see current()
+ * @return void
+ */
+ public function next()
+ {
+ // fetch next entry.
+ // if we have no entrys anymore, we add false (which is
+ // returned by shiftEntry()) so current() will complain.
+ if (count($this->_iteratorCache) - 1 <= $this->count()) {
+ $this->_iteratorCache[] = $this->shiftEntry();
+ }
+
+ // move on array pointer to current element.
+ // even if we have added all entries, this will
+ // ensure proper operation in case we rewind()
+ next($this->_iteratorCache);
+ }
+
+ /**
+ * SPL Iterator interface: Check if there is a current element after calls to {@link rewind()} or {@link next()}.
+ *
+ * Used to check if we've iterated to the end of the collection.
+ *
+ * @see current()
+ * @return boolean FALSE if there's nothing more to iterate over
+ */
+ public function valid()
+ {
+ return ($this->current() instanceof Net_LDAP2_Entry);
+ }
+
+ /**
+ * SPL Iterator interface: Rewind the Iterator to the first element.
+ *
+ * After rewinding, {@link current()} will return the first entry in the result set.
+ *
+ * @see current()
+ * @return void
+ */
+ public function rewind()
+ {
+ reset($this->_iteratorCache);
+ }
+}
+
+?>
diff --git a/extlib/Net/LDAP2/SimpleFileSchemaCache.php b/extlib/Net/LDAP2/SimpleFileSchemaCache.php
new file mode 100644
index 000000000..8019654ac
--- /dev/null
+++ b/extlib/Net/LDAP2/SimpleFileSchemaCache.php
@@ -0,0 +1,97 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the example simple file based Schema Caching class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: SimpleFileSchemaCache.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* A simple file based schema cacher with cache aging.
+*
+* Once the cache is too old, the loadSchema() method will return false, so
+* Net_LDAP2 will fetch a fresh object from the LDAP server that will
+* overwrite the current (outdated) old cache.
+*/
+class Net_LDAP2_SimpleFileSchemaCache implements Net_LDAP2_SchemaCache
+{
+ /**
+ * Internal config of this cache
+ *
+ * @see Net_LDAP2_SimpleFileSchemaCache()
+ * @var array
+ */
+ protected $config = array(
+ 'path' => '/tmp/Net_LDAP_Schema.cache',
+ 'max_age' => 1200
+ );
+
+ /**
+ * Initialize the simple cache
+ *
+ * Config is as following:
+ * path Complete path to the cache file.
+ * max_age Maximum age of cache in seconds, 0 means "endlessly".
+ *
+ * @param array $cfg Config array
+ */
+ public function Net_LDAP2_SimpleFileSchemaCache($cfg)
+ {
+ foreach ($cfg as $key => $value) {
+ if (array_key_exists($key, $this->config)) {
+ if (gettype($this->config[$key]) != gettype($value)) {
+ $this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key does not match type ".gettype($this->config[$key])."!");
+ }
+ $this->config[$key] = $value;
+ } else {
+ $this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key is not defined!");
+ }
+ }
+ }
+
+ /**
+ * Return the schema object from the cache
+ *
+ * If file is existent and cache has not expired yet,
+ * then the cache is deserialized and returned.
+ *
+ * @return Net_LDAP2_Schema|Net_LDAP2_Error|false
+ */
+ public function loadSchema()
+ {
+ $return = false; // Net_LDAP2 will load schema from LDAP
+ if (file_exists($this->config['path'])) {
+ $cache_maxage = filemtime($this->config['path']) + $this->config['max_age'];
+ if (time() <= $cache_maxage || $this->config['max_age'] == 0) {
+ $return = unserialize(file_get_contents($this->config['path']));
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Store a schema object in the cache
+ *
+ * This method will be called, if Net_LDAP2 has fetched a fresh
+ * schema object from LDAP and wants to init or refresh the cache.
+ *
+ * To invalidate the cache and cause Net_LDAP2 to refresh the cache,
+ * you can call this method with null or false as value.
+ * The next call to $ldap->schema() will then refresh the caches object.
+ *
+ * @param mixed $schema The object that should be cached
+ * @return true|Net_LDAP2_Error|false
+ */
+ public function storeSchema($schema) {
+ file_put_contents($this->config['path'], serialize($schema));
+ return true;
+ }
+}
diff --git a/extlib/Net/LDAP2/Util.php b/extlib/Net/LDAP2/Util.php
new file mode 100644
index 000000000..48b03f9f9
--- /dev/null
+++ b/extlib/Net/LDAP2/Util.php
@@ -0,0 +1,572 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+* File containing the Net_LDAP2_Util interface class.
+*
+* PHP version 5
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @copyright 2009 Benedikt Hallinger
+* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
+* @version SVN: $Id: Util.php 286718 2009-08-03 07:30:49Z beni $
+* @link http://pear.php.net/package/Net_LDAP2/
+*/
+
+/**
+* Includes
+*/
+require_once 'PEAR.php';
+
+/**
+* Utility Class for Net_LDAP2
+*
+* This class servers some functionality to the other classes of Net_LDAP2 but most of
+* the methods can be used separately as well.
+*
+* @category Net
+* @package Net_LDAP2
+* @author Benedikt Hallinger <beni@php.net>
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+* @link http://pear.php.net/package/Net_LDAP22/
+*/
+class Net_LDAP2_Util extends PEAR
+{
+ /**
+ * Constructor
+ *
+ * @access public
+ */
+ public function __construct()
+ {
+ // We do nothing here, since all methods can be called statically.
+ // In Net_LDAP <= 0.7, we needed a instance of Util, because
+ // it was possible to do utf8 encoding and decoding, but this
+ // has been moved to the LDAP class. The constructor remains only
+ // here to document the downward compatibility of creating an instance.
+ }
+
+ /**
+ * Explodes the given DN into its elements
+ *
+ * {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence
+ * of Relative Distinguished Names (RDNs), which themselves
+ * are sets of Attributes. For each RDN a array is constructed where the RDN part is stored.
+ *
+ * For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
+ * <kbd>array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' )</kbd>
+ *
+ * [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of
+ * the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded
+ * and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to
+ * the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
+ * [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ];
+ * See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER.
+ *
+ * It also performs the following operations on the given DN:
+ * - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair
+ * and strings beginning with "#".
+ * - Removes the leading 'OID.' characters if the type is an OID instead of a name.
+ * - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
+ *
+ * OPTIONS is a list of name/value pairs, valid options are:
+ * casefold Controls case folding of attribute types names.
+ * Attribute values are not affected by this option.
+ * The default is to uppercase. Valid values are:
+ * lower Lowercase attribute types names.
+ * upper Uppercase attribute type names. This is the default.
+ * none Do not change attribute type names.
+ * reverse If TRUE, the RDN sequence is reversed.
+ * onlyvalues If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo')
+ *
+
+ * @param string $dn The DN that should be exploded
+ * @param array $options Options to use
+ *
+ * @static
+ * @return array Parts of the exploded DN
+ * @todo implement BER
+ */
+ public static function ldap_explode_dn($dn, $options = array('casefold' => 'upper'))
+ {
+ if (!isset($options['onlyvalues'])) $options['onlyvalues'] = false;
+ if (!isset($options['reverse'])) $options['reverse'] = false;
+ if (!isset($options['casefold'])) $options['casefold'] = 'upper';
+
+ // Escaping of DN and stripping of "OID."
+ $dn = self::canonical_dn($dn, array('casefold' => $options['casefold']));
+
+ // splitting the DN
+ $dn_array = preg_split('/(?<=[^\\\\]),/', $dn);
+
+ // clear wrong splitting (possibly we have split too much)
+ // /!\ Not clear, if this is neccessary here
+ //$dn_array = self::correct_dn_splitting($dn_array, ',');
+
+ // construct subarrays for multivalued RDNs and unescape DN value
+ // also convert to output format and apply casefolding
+ foreach ($dn_array as $key => $value) {
+ $value_u = self::unescape_dn_value($value);
+ $rdns = self::split_rdn_multival($value_u[0]);
+ if (count($rdns) > 1) {
+ // MV RDN!
+ foreach ($rdns as $subrdn_k => $subrdn_v) {
+ // Casefolding
+ if ($options['casefold'] == 'upper') $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $subrdn_v);
+ if ($options['casefold'] == 'lower') $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $subrdn_v);
+
+ if ($options['onlyvalues']) {
+ preg_match('/(.+?)(?<!\\\\)=(.+)/', $subrdn_v, $matches);
+ $rdn_ocl = $matches[1];
+ $rdn_val = $matches[2];
+ $unescaped = self::unescape_dn_value($rdn_val);
+ $rdns[$subrdn_k] = $unescaped[0];
+ } else {
+ $unescaped = self::unescape_dn_value($subrdn_v);
+ $rdns[$subrdn_k] = $unescaped[0];
+ }
+ }
+
+ $dn_array[$key] = $rdns;
+ } else {
+ // normal RDN
+
+ // Casefolding
+ if ($options['casefold'] == 'upper') $value = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $value);
+ if ($options['casefold'] == 'lower') $value = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $value);
+
+ if ($options['onlyvalues']) {
+ preg_match('/(.+?)(?<!\\\\)=(.+)/', $value, $matches);
+ $dn_ocl = $matches[1];
+ $dn_val = $matches[2];
+ $unescaped = self::unescape_dn_value($dn_val);
+ $dn_array[$key] = $unescaped[0];
+ } else {
+ $unescaped = self::unescape_dn_value($value);
+ $dn_array[$key] = $unescaped[0];
+ }
+ }
+ }
+
+ if ($options['reverse']) {
+ return array_reverse($dn_array);
+ } else {
+ return $dn_array;
+ }
+ }
+
+ /**
+ * Escapes a DN value according to RFC 2253
+ *
+ * Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
+ * The characters ",", "+", """, "\", "<", ">", ";", "#", "=" with a special meaning in RFC 2252
+ * are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
+ * Finally all leading and trailing spaces are converted to sequences of \20.
+ *
+ * @param array $values An array containing the DN values that should be escaped
+ *
+ * @static
+ * @return array The array $values, but escaped
+ */
+ public static function escape_dn_value($values = array())
+ {
+ // Parameter validation
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ foreach ($values as $key => $val) {
+ // Escaping of filter meta characters
+ $val = str_replace('\\', '\\\\', $val);
+ $val = str_replace(',', '\,', $val);
+ $val = str_replace('+', '\+', $val);
+ $val = str_replace('"', '\"', $val);
+ $val = str_replace('<', '\<', $val);
+ $val = str_replace('>', '\>', $val);
+ $val = str_replace(';', '\;', $val);
+ $val = str_replace('#', '\#', $val);
+ $val = str_replace('=', '\=', $val);
+
+ // ASCII < 32 escaping
+ $val = self::asc2hex32($val);
+
+ // Convert all leading and trailing spaces to sequences of \20.
+ if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
+ $val = $matches[2];
+ for ($i = 0; $i < strlen($matches[1]); $i++) {
+ $val = '\20'.$val;
+ }
+ for ($i = 0; $i < strlen($matches[3]); $i++) {
+ $val = $val.'\20';
+ }
+ }
+
+ if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
+
+ $values[$key] = $val;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Undoes the conversion done by escape_dn_value().
+ *
+ * Any escape sequence starting with a baskslash - hexpair or special character -
+ * will be transformed back to the corresponding character.
+ *
+ * @param array $values Array of DN Values
+ *
+ * @return array Same as $values, but unescaped
+ * @static
+ */
+ public static function unescape_dn_value($values = array())
+ {
+ // Parameter validation
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ foreach ($values as $key => $val) {
+ // strip slashes from special chars
+ $val = str_replace('\\\\', '\\', $val);
+ $val = str_replace('\,', ',', $val);
+ $val = str_replace('\+', '+', $val);
+ $val = str_replace('\"', '"', $val);
+ $val = str_replace('\<', '<', $val);
+ $val = str_replace('\>', '>', $val);
+ $val = str_replace('\;', ';', $val);
+ $val = str_replace('\#', '#', $val);
+ $val = str_replace('\=', '=', $val);
+
+ // Translate hex code into ascii
+ $values[$key] = self::hex2asc($val);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns the given DN in a canonical form
+ *
+ * Returns false if DN is not a valid Distinguished Name.
+ * DN can either be a string or an array
+ * as returned by ldap_explode_dn, which is useful when constructing a DN.
+ * The DN array may have be indexed (each array value is a OCL=VALUE pair)
+ * or associative (array key is OCL and value is VALUE).
+ *
+ * It performs the following operations on the given DN:
+ * - Removes the leading 'OID.' characters if the type is an OID instead of a name.
+ * - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair.
+ * - Converts all leading and trailing spaces in values to be \20.
+ * - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
+ *
+ * OPTIONS is a list of name/value pairs, valid options are:
+ * casefold Controls case folding of attribute type names.
+ * Attribute values are not affected by this option. The default is to uppercase.
+ * Valid values are:
+ * lower Lowercase attribute type names.
+ * upper Uppercase attribute type names. This is the default.
+ * none Do not change attribute type names.
+ * [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}.
+ * reverse If TRUE, the RDN sequence is reversed.
+ * separator Separator to use between RDNs. Defaults to comma (',').
+ *
+ * Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test,
+ * because an empty string evaluates to false. Use the "===" operator instead.
+ *
+ * @param array|string $dn The DN
+ * @param array $options Options to use
+ *
+ * @static
+ * @return false|string The canonical DN or FALSE
+ * @todo implement option mbcescape
+ */
+ public static function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ','))
+ {
+ if ($dn === '') return $dn; // empty DN is valid!
+
+ // options check
+ if (!isset($options['reverse'])) {
+ $options['reverse'] = false;
+ } else {
+ $options['reverse'] = true;
+ }
+ if (!isset($options['casefold'])) $options['casefold'] = 'upper';
+ if (!isset($options['separator'])) $options['separator'] = ',';
+
+
+ if (!is_array($dn)) {
+ // It is not clear to me if the perl implementation splits by the user defined
+ // separator or if it just uses this separator to construct the new DN
+ $dn = preg_split('/(?<=[^\\\\])'.$options['separator'].'/', $dn);
+
+ // clear wrong splitting (possibly we have split too much)
+ $dn = self::correct_dn_splitting($dn, $options['separator']);
+ } else {
+ // Is array, check, if the array is indexed or associative
+ $assoc = false;
+ foreach ($dn as $dn_key => $dn_part) {
+ if (!is_int($dn_key)) {
+ $assoc = true;
+ }
+ }
+ // convert to indexed, if associative array detected
+ if ($assoc) {
+ $newdn = array();
+ foreach ($dn as $dn_key => $dn_part) {
+ if (is_array($dn_part)) {
+ ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative
+ $newdn[] = $dn_part; // copy array as-is, so we can resolve it later
+ } else {
+ $newdn[] = $dn_key.'='.$dn_part;
+ }
+ }
+ $dn =& $newdn;
+ }
+ }
+
+ // Escaping and casefolding
+ foreach ($dn as $pos => $dnval) {
+ if (is_array($dnval)) {
+ // subarray detected, this means very surely, that we had
+ // a multivalued dn part, which must be resolved
+ $dnval_new = '';
+ foreach ($dnval as $subkey => $subval) {
+ // build RDN part
+ if (!is_int($subkey)) {
+ $subval = $subkey.'='.$subval;
+ }
+ $subval_processed = self::canonical_dn($subval);
+ if (false === $subval_processed) return false;
+ $dnval_new .= $subval_processed.'+';
+ }
+ $dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus
+ } else {
+ // try to split multivalued RDNS into array
+ $rdns = self::split_rdn_multival($dnval);
+ if (count($rdns) > 1) {
+ // Multivalued RDN was detected!
+ // The RDN value is expected to be correctly split by split_rdn_multival().
+ // It's time to sort the RDN and build the DN!
+ $rdn_string = '';
+ sort($rdns, SORT_STRING); // Sort RDN keys alphabetically
+ foreach ($rdns as $rdn) {
+ $subval_processed = self::canonical_dn($rdn);
+ if (false === $subval_processed) return false;
+ $rdn_string .= $subval_processed.'+';
+ }
+
+ $dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus
+
+ } else {
+ // no multivalued RDN!
+ // split at first unescaped "="
+ $dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2);
+ $ocl = ltrim($dn_comp[0]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma)
+ $val = $dn_comp[1];
+
+ // strip 'OID.', otherwise apply casefolding and escaping
+ if (substr(strtolower($ocl), 0, 4) == 'oid.') {
+ $ocl = substr($ocl, 4);
+ } else {
+ if ($options['casefold'] == 'upper') $ocl = strtoupper($ocl);
+ if ($options['casefold'] == 'lower') $ocl = strtolower($ocl);
+ $ocl = self::escape_dn_value(array($ocl));
+ $ocl = $ocl[0];
+ }
+
+ // escaping of dn-value
+ $val = self::escape_dn_value(array($val));
+ $val = str_replace('/', '\/', $val[0]);
+
+ $dn[$pos] = $ocl.'='.$val;
+ }
+ }
+ }
+
+ if ($options['reverse']) $dn = array_reverse($dn);
+ return implode($options['separator'], $dn);
+ }
+
+ /**
+ * Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
+ *
+ * Any control characters with an ACII code < 32 as well as the characters with special meaning in
+ * LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
+ * backslash followed by two hex digits representing the hexadecimal value of the character.
+ *
+ * @param array $values Array of values to escape
+ *
+ * @static
+ * @return array Array $values, but escaped
+ */
+ public static function escape_filter_value($values = array())
+ {
+ // Parameter validation
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ foreach ($values as $key => $val) {
+ // Escaping of filter meta characters
+ $val = str_replace('\\', '\5c', $val);
+ $val = str_replace('*', '\2a', $val);
+ $val = str_replace('(', '\28', $val);
+ $val = str_replace(')', '\29', $val);
+
+ // ASCII < 32 escaping
+ $val = self::asc2hex32($val);
+
+ if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
+
+ $values[$key] = $val;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Undoes the conversion done by {@link escape_filter_value()}.
+ *
+ * Converts any sequences of a backslash followed by two hex digits into the corresponding character.
+ *
+ * @param array $values Array of values to escape
+ *
+ * @static
+ * @return array Array $values, but unescaped
+ */
+ public static function unescape_filter_value($values = array())
+ {
+ // Parameter validation
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ foreach ($values as $key => $value) {
+ // Translate hex code into ascii
+ $values[$key] = self::hex2asc($value);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Converts all ASCII chars < 32 to "\HEX"
+ *
+ * @param string $string String to convert
+ *
+ * @static
+ * @return string
+ */
+ public static function asc2hex32($string)
+ {
+ for ($i = 0; $i < strlen($string); $i++) {
+ $char = substr($string, $i, 1);
+ if (ord($char) < 32) {
+ $hex = dechex(ord($char));
+ if (strlen($hex) == 1) $hex = '0'.$hex;
+ $string = str_replace($char, '\\'.$hex, $string);
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Converts all Hex expressions ("\HEX") to their original ASCII characters
+ *
+ * @param string $string String to convert
+ *
+ * @static
+ * @author beni@php.net, heavily based on work from DavidSmith@byu.net
+ * @return string
+ */
+ public static function hex2asc($string)
+ {
+ $string = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $string);
+ return $string;
+ }
+
+ /**
+ * Split an multivalued RDN value into an Array
+ *
+ * A RDN can contain multiple values, spearated by a plus sign.
+ * This function returns each separate ocl=value pair of the RDN part.
+ *
+ * If no multivalued RDN is detected, an array containing only
+ * the original rdn part is returned.
+ *
+ * For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to:
+ * <kbd>array([0] => 'OU=Sales', [1] => 'CN=J. Smith')</kbd>
+ *
+ * The method trys to be smart if it encounters unescaped "+" characters, but may fail,
+ * so ensure escaped "+"es in attr names and attr values.
+ *
+ * [BUG] If you have a multivalued RDN with unescaped plus characters
+ * and there is a unescaped plus sign at the end of an value followed by an
+ * attribute name containing an unescaped plus, then you will get wrong splitting:
+ * $rdn = 'OU=Sales+C+N=J. Smith';
+ * returns:
+ * array('OU=Sales+C', 'N=J. Smith');
+ * The "C+" is treaten as value of the first pair instead as attr name of the second pair.
+ * To prevent this, escape correctly.
+ *
+ * @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar)
+ *
+ * @static
+ * @return array Array with the components of the multivalued RDN or Error
+ */
+ public static function split_rdn_multival($rdn)
+ {
+ $rdns = preg_split('/(?<!\\\\)\+/', $rdn);
+ $rdns = self::correct_dn_splitting($rdns, '+');
+ return array_values($rdns);
+ }
+
+ /**
+ * Splits a attribute=value syntax into an array
+ *
+ * The split will occur at the first unescaped '=' character.
+ *
+ * @param string $attr Attribute and Value Syntax
+ *
+ * @return array Indexed array: 0=attribute name, 1=attribute value
+ */
+ public static function split_attribute_string($attr)
+ {
+ return preg_split('/(?<!\\\\)=/', $attr, 2);
+ }
+
+ /**
+ * Corrects splitting of dn parts
+ *
+ * @param array $dn Raw DN array
+ * @param array $separator Separator that was used when splitting
+ *
+ * @return array Corrected array
+ * @access protected
+ */
+ protected static function correct_dn_splitting($dn = array(), $separator = ',')
+ {
+ foreach ($dn as $key => $dn_value) {
+ $dn_value = $dn[$key]; // refresh value (foreach caches!)
+ // if the dn_value is not in attr=value format, then we had an
+ // unescaped separator character inside the attr name or the value.
+ // We assume, that it was the attribute value.
+ // [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class
+ // must remain independent from the other classes or connections.
+ if (!preg_match('/.+(?<!\\\\)=.+/', $dn_value)) {
+ unset($dn[$key]);
+ if (array_key_exists($key-1, $dn)) {
+ $dn[$key-1] = $dn[$key-1].$separator.$dn_value; // append to previous attr value
+ } else {
+ $dn[$key+1] = $dn_value.$separator.$dn[$key+1]; // first element: prepend to next attr name
+ }
+ }
+ }
+ return array_values($dn);
+ }
+}
+
+?>
diff --git a/extlib/Net/URL2.php b/extlib/Net/URL2.php
index 7a654aed8..f7fbcd9ce 100644
--- a/extlib/Net/URL2.php
+++ b/extlib/Net/URL2.php
@@ -1,44 +1,58 @@
<?php
-// +-----------------------------------------------------------------------+
-// | Copyright (c) 2007-2008, Christian Schmidt, Peytz & Co. A/S |
-// | All rights reserved. |
-// | |
-// | Redistribution and use in source and binary forms, with or without |
-// | modification, are permitted provided that the following conditions |
-// | are met: |
-// | |
-// | o Redistributions of source code must retain the above copyright |
-// | notice, this list of conditions and the following disclaimer. |
-// | o Redistributions in binary form must reproduce the above copyright |
-// | notice, this list of conditions and the following disclaimer in the |
-// | documentation and/or other materials provided with the distribution.|
-// | o The names of the authors may not be used to endorse or promote |
-// | products derived from this software without specific prior written |
-// | permission. |
-// | |
-// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
-// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
-// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
-// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
-// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
-// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
-// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
-// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// | |
-// +-----------------------------------------------------------------------+
-// | Author: Christian Schmidt <schmidt at php dot net> |
-// +-----------------------------------------------------------------------+
-//
-// $Id: URL2.php,v 1.10 2008/04/26 21:57:08 schmidt Exp $
-//
-// Net_URL2 Class (PHP5 Only)
-
-// This code is released under the BSD License - http://www.opensource.org/licenses/bsd-license.php
/**
- * @license BSD License
+ * Net_URL2, a class representing a URL as per RFC 3986.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2007-2009, Peytz & Co. A/S
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * * Neither the name of the PHP_LexerGenerator nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category Networking
+ * @package Net_URL2
+ * @author Christian Schmidt <chsc@peytz.dk>
+ * @copyright 2007-2008 Peytz & Co. A/S
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: URL2.php 286661 2009-08-02 12:50:54Z schmidt $
+ * @link http://www.rfc-editor.org/rfc/rfc3986.txt
+ */
+
+/**
+ * Represents a URL as per RFC 3986.
+ *
+ * @category Networking
+ * @package Net_URL2
+ * @author Christian Schmidt <chsc@peytz.dk>
+ * @copyright 2007-2008 Peytz & Co. ApS
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Net_URL2
*/
class Net_URL2
{
@@ -46,24 +60,24 @@ class Net_URL2
* Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
* is true.
*/
- const OPTION_STRICT = 'strict';
+ const OPTION_STRICT = 'strict';
/**
* Represent arrays in query using PHP's [] notation. Default is true.
*/
- const OPTION_USE_BRACKETS = 'use_brackets';
+ const OPTION_USE_BRACKETS = 'use_brackets';
/**
* URL-encode query variable keys. Default is true.
*/
- const OPTION_ENCODE_KEYS = 'encode_keys';
+ const OPTION_ENCODE_KEYS = 'encode_keys';
/**
* Query variable separators when parsing the query string. Every character
* is considered a separator. Default is specified by the
* arg_separator.input php.ini setting (this defaults to "&").
*/
- const OPTION_SEPARATOR_INPUT = 'input_separator';
+ const OPTION_SEPARATOR_INPUT = 'input_separator';
/**
* Query variable separator used when generating the query string. Default
@@ -75,7 +89,7 @@ class Net_URL2
/**
* Default options corresponds to how PHP handles $_GET.
*/
- private $options = array(
+ private $_options = array(
self::OPTION_STRICT => true,
self::OPTION_USE_BRACKETS => true,
self::OPTION_ENCODE_KEYS => true,
@@ -86,41 +100,43 @@ class Net_URL2
/**
* @var string|bool
*/
- private $scheme = false;
+ private $_scheme = false;
/**
* @var string|bool
*/
- private $userinfo = false;
+ private $_userinfo = false;
/**
* @var string|bool
*/
- private $host = false;
+ private $_host = false;
/**
* @var int|bool
*/
- private $port = false;
+ private $_port = false;
/**
* @var string
*/
- private $path = '';
+ private $_path = '';
/**
* @var string|bool
*/
- private $query = false;
+ private $_query = false;
/**
* @var string|bool
*/
- private $fragment = false;
+ private $_fragment = false;
/**
+ * Constructor.
+ *
* @param string $url an absolute or relative URL
- * @param array $options
+ * @param array $options an array of OPTION_xxx constants
*/
public function __construct($url, $options = null)
{
@@ -130,12 +146,12 @@ class Net_URL2
ini_get('arg_separator.output'));
if (is_array($options)) {
foreach ($options as $optionName => $value) {
- $this->setOption($optionName);
+ $this->setOption($optionName, $value);
}
}
if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) {
- $this->scheme = $reg[1];
+ $this->_scheme = $reg[1];
$url = substr($url, strlen($reg[0]));
}
@@ -145,20 +161,59 @@ class Net_URL2
}
$i = strcspn($url, '?#');
- $this->path = substr($url, 0, $i);
+ $this->_path = substr($url, 0, $i);
$url = substr($url, $i);
if (preg_match('@^\?([^#]*)@', $url, $reg)) {
- $this->query = $reg[1];
+ $this->_query = $reg[1];
$url = substr($url, strlen($reg[0]));
}
if ($url) {
- $this->fragment = substr($url, 1);
+ $this->_fragment = substr($url, 1);
}
}
/**
+ * Magic Setter.
+ *
+ * This method will magically set the value of a private variable ($var)
+ * with the value passed as the args
+ *
+ * @param string $var The private variable to set.
+ * @param mixed $arg An argument of any type.
+ * @return void
+ */
+ public function __set($var, $arg)
+ {
+ $method = 'set' . $var;
+ if (method_exists($this, $method)) {
+ $this->$method($arg);
+ }
+ }
+
+ /**
+ * Magic Getter.
+ *
+ * This is the magic get method to retrieve the private variable
+ * that was set by either __set() or it's setter...
+ *
+ * @param string $var The property name to retrieve.
+ * @return mixed $this->$var Either a boolean false if the
+ * property is not set or the value
+ * of the private property.
+ */
+ public function __get($var)
+ {
+ $method = 'get' . $var;
+ if (method_exists($this, $method)) {
+ return $this->$method();
+ }
+
+ return false;
+ }
+
+ /**
* Returns the scheme, e.g. "http" or "urn", or false if there is no
* scheme specified, i.e. if this is a relative URL.
*
@@ -166,18 +221,23 @@ class Net_URL2
*/
public function getScheme()
{
- return $this->scheme;
+ return $this->_scheme;
}
/**
- * @param string|bool $scheme
+ * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
+ * scheme specified, i.e. if this is a relative URL.
+ *
+ * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
+ * scheme specified, i.e. if this is a relative
+ * URL
*
* @return void
* @see getScheme()
*/
public function setScheme($scheme)
{
- $this->scheme = $scheme;
+ $this->_scheme = $scheme;
}
/**
@@ -188,7 +248,9 @@ class Net_URL2
*/
public function getUser()
{
- return $this->userinfo !== false ? preg_replace('@:.*$@', '', $this->userinfo) : false;
+ return $this->_userinfo !== false
+ ? preg_replace('@:.*$@', '', $this->_userinfo)
+ : false;
}
/**
@@ -201,7 +263,9 @@ class Net_URL2
*/
public function getPassword()
{
- return $this->userinfo !== false ? substr(strstr($this->userinfo, ':'), 1) : false;
+ return $this->_userinfo !== false
+ ? substr(strstr($this->_userinfo, ':'), 1)
+ : false;
}
/**
@@ -212,7 +276,7 @@ class Net_URL2
*/
public function getUserinfo()
{
- return $this->userinfo;
+ return $this->_userinfo;
}
/**
@@ -220,15 +284,15 @@ class Net_URL2
* in the userinfo part as username ":" password.
*
* @param string|bool $userinfo userinfo or username
- * @param string|bool $password
+ * @param string|bool $password optional password, or false
*
* @return void
*/
public function setUserinfo($userinfo, $password = false)
{
- $this->userinfo = $userinfo;
+ $this->_userinfo = $userinfo;
if ($password !== false) {
- $this->userinfo .= ':' . $password;
+ $this->_userinfo .= ':' . $password;
}
}
@@ -236,21 +300,24 @@ class Net_URL2
* Returns the host part, or false if there is no authority part, e.g.
* relative URLs.
*
- * @return string|bool
+ * @return string|bool a hostname, an IP address, or false
*/
public function getHost()
{
- return $this->host;
+ return $this->_host;
}
/**
- * @param string|bool $host
+ * Sets the host part. Specify false if there is no authority part, e.g.
+ * relative URLs.
+ *
+ * @param string|bool $host a hostname, an IP address, or false
*
* @return void
*/
public function setHost($host)
{
- $this->host = $host;
+ $this->_host = $host;
}
/**
@@ -261,65 +328,72 @@ class Net_URL2
*/
public function getPort()
{
- return $this->port;
+ return $this->_port;
}
/**
- * @param int|bool $port
+ * Sets the port number. Specify false if there is no port number specified,
+ * i.e. if the default port is to be used.
+ *
+ * @param int|bool $port a port number, or false
*
* @return void
*/
public function setPort($port)
{
- $this->port = intval($port);
+ $this->_port = intval($port);
}
/**
* Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
- * false if there is no authority none.
+ * false if there is no authority.
*
* @return string|bool
*/
public function getAuthority()
{
- if (!$this->host) {
+ if (!$this->_host) {
return false;
}
$authority = '';
- if ($this->userinfo !== false) {
- $authority .= $this->userinfo . '@';
+ if ($this->_userinfo !== false) {
+ $authority .= $this->_userinfo . '@';
}
- $authority .= $this->host;
+ $authority .= $this->_host;
- if ($this->port !== false) {
- $authority .= ':' . $this->port;
+ if ($this->_port !== false) {
+ $authority .= ':' . $this->_port;
}
return $authority;
}
/**
- * @param string|false $authority
+ * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
+ * false if there is no authority.
+ *
+ * @param string|false $authority a hostname or an IP addresse, possibly
+ * with userinfo prefixed and port number
+ * appended, e.g. "foo:bar@example.org:81".
*
* @return void
*/
public function setAuthority($authority)
{
- $this->user = false;
- $this->pass = false;
- $this->host = false;
- $this->port = false;
- if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
+ $this->_userinfo = false;
+ $this->_host = false;
+ $this->_port = false;
+ if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
if ($reg[1]) {
- $this->userinfo = $reg[2];
+ $this->_userinfo = $reg[2];
}
- $this->host = $reg[3];
+ $this->_host = $reg[3];
if (isset($reg[5])) {
- $this->port = intval($reg[5]);
+ $this->_port = intval($reg[5]);
}
}
}
@@ -331,65 +405,74 @@ class Net_URL2
*/
public function getPath()
{
- return $this->path;
+ return $this->_path;
}
/**
- * @param string $path
+ * Sets the path part (possibly an empty string).
+ *
+ * @param string $path a path
*
* @return void
*/
public function setPath($path)
{
- $this->path = $path;
+ $this->_path = $path;
}
/**
* Returns the query string (excluding the leading "?"), or false if "?"
- * isn't present in the URL.
+ * is not present in the URL.
*
* @return string|bool
* @see self::getQueryVariables()
*/
public function getQuery()
{
- return $this->query;
+ return $this->_query;
}
/**
- * @param string|bool $query
+ * Sets the query string (excluding the leading "?"). Specify false if "?"
+ * is not present in the URL.
+ *
+ * @param string|bool $query a query string, e.g. "foo=1&bar=2"
*
* @return void
* @see self::setQueryVariables()
*/
public function setQuery($query)
{
- $this->query = $query;
+ $this->_query = $query;
}
/**
- * Returns the fragment name, or false if "#" isn't present in the URL.
+ * Returns the fragment name, or false if "#" is not present in the URL.
*
* @return string|bool
*/
public function getFragment()
{
- return $this->fragment;
+ return $this->_fragment;
}
/**
- * @param string|bool $fragment
+ * Sets the fragment name. Specify false if "#" is not present in the URL.
+ *
+ * @param string|bool $fragment a fragment excluding the leading "#", or
+ * false
*
* @return void
*/
public function setFragment($fragment)
{
- $this->fragment = $fragment;
+ $this->_fragment = $fragment;
}
/**
* Returns the query string like an array as the variables would appear in
- * $_GET in a PHP script.
+ * $_GET in a PHP script. If the URL does not contain a "?", an empty array
+ * is returned.
*
* @return array
*/
@@ -398,7 +481,7 @@ class Net_URL2
$pattern = '/[' .
preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
']/';
- $parts = preg_split($pattern, $this->query, -1, PREG_SPLIT_NO_EMPTY);
+ $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
$return = array();
foreach ($parts as $part) {
@@ -445,6 +528,8 @@ class Net_URL2
}
/**
+ * Sets the query string to the specified variable in the query string.
+ *
* @param array $array (name => value) array
*
* @return void
@@ -452,11 +537,11 @@ class Net_URL2
public function setQueryVariables(array $array)
{
if (!$array) {
- $this->query = false;
+ $this->_query = false;
} else {
foreach ($array as $name => $value) {
if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
- $name = rawurlencode($name);
+ $name = self::urlencode($name);
}
if (is_array($value)) {
@@ -466,19 +551,21 @@ class Net_URL2
: ($name . '=' . $v);
}
} elseif (!is_null($value)) {
- $parts[] = $name . '=' . $value;
+ $parts[] = $name . '=' . self::urlencode($value);
} else {
$parts[] = $name;
}
}
- $this->query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
- $parts);
+ $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
+ $parts);
}
}
/**
- * @param string $name
- * @param mixed $value
+ * Sets the specified variable in the query string.
+ *
+ * @param string $name variable name
+ * @param mixed $value variable value
*
* @return array
*/
@@ -490,7 +577,9 @@ class Net_URL2
}
/**
- * @param string $name
+ * Removes the specifed variable from the query string.
+ *
+ * @param string $name a query string variable, e.g. "foo" in "?foo=1"
*
* @return void
*/
@@ -511,27 +600,38 @@ class Net_URL2
// See RFC 3986, section 5.3
$url = "";
- if ($this->scheme !== false) {
- $url .= $this->scheme . ':';
+ if ($this->_scheme !== false) {
+ $url .= $this->_scheme . ':';
}
$authority = $this->getAuthority();
if ($authority !== false) {
$url .= '//' . $authority;
}
- $url .= $this->path;
+ $url .= $this->_path;
- if ($this->query !== false) {
- $url .= '?' . $this->query;
+ if ($this->_query !== false) {
+ $url .= '?' . $this->_query;
}
- if ($this->fragment !== false) {
- $url .= '#' . $this->fragment;
+ if ($this->_fragment !== false) {
+ $url .= '#' . $this->_fragment;
}
return $url;
}
+ /**
+ * Returns a string representation of this URL.
+ *
+ * @return string
+ * @see toString()
+ */
+ public function __toString()
+ {
+ return $this->getURL();
+ }
+
/**
* Returns a normalized string representation of this URL. This is useful
* for comparison of URLs.
@@ -555,36 +655,38 @@ class Net_URL2
// See RFC 3886, section 6
// Schemes are case-insensitive
- if ($this->scheme) {
- $this->scheme = strtolower($this->scheme);
+ if ($this->_scheme) {
+ $this->_scheme = strtolower($this->_scheme);
}
// Hostnames are case-insensitive
- if ($this->host) {
- $this->host = strtolower($this->host);
+ if ($this->_host) {
+ $this->_host = strtolower($this->_host);
}
// Remove default port number for known schemes (RFC 3986, section 6.2.3)
- if ($this->port &&
- $this->scheme &&
- $this->port == getservbyname($this->scheme, 'tcp')) {
+ if ($this->_port &&
+ $this->_scheme &&
+ $this->_port == getservbyname($this->_scheme, 'tcp')) {
- $this->port = false;
+ $this->_port = false;
}
// Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
- foreach (array('userinfo', 'host', 'path') as $part) {
+ foreach (array('_userinfo', '_host', '_path') as $part) {
if ($this->$part) {
- $this->$part = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $this->$part);
+ $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
+ 'strtoupper("\0")',
+ $this->$part);
}
}
// Path segment normalization (RFC 3986, section 6.2.2.3)
- $this->path = self::removeDotSegments($this->path);
+ $this->_path = self::removeDotSegments($this->_path);
// Scheme based normalization (RFC 3986, section 6.2.3)
- if ($this->host && !$this->path) {
- $this->path = '/';
+ if ($this->_host && !$this->_path) {
+ $this->_path = '/';
}
}
@@ -595,7 +697,7 @@ class Net_URL2
*/
public function isAbsolute()
{
- return (bool) $this->scheme;
+ return (bool) $this->_scheme;
}
/**
@@ -608,7 +710,7 @@ class Net_URL2
*/
public function resolve($reference)
{
- if (is_string($reference)) {
+ if (!$reference instanceof Net_URL2) {
$reference = new self($reference);
}
if (!$this->isAbsolute()) {
@@ -617,54 +719,54 @@ class Net_URL2
// A non-strict parser may ignore a scheme in the reference if it is
// identical to the base URI's scheme.
- if (!$this->getOption(self::OPTION_STRICT) && $reference->scheme == $this->scheme) {
- $reference->scheme = false;
+ if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
+ $reference->_scheme = false;
}
$target = new self('');
- if ($reference->scheme !== false) {
- $target->scheme = $reference->scheme;
+ if ($reference->_scheme !== false) {
+ $target->_scheme = $reference->_scheme;
$target->setAuthority($reference->getAuthority());
- $target->path = self::removeDotSegments($reference->path);
- $target->query = $reference->query;
+ $target->_path = self::removeDotSegments($reference->_path);
+ $target->_query = $reference->_query;
} else {
$authority = $reference->getAuthority();
if ($authority !== false) {
$target->setAuthority($authority);
- $target->path = self::removeDotSegments($reference->path);
- $target->query = $reference->query;
+ $target->_path = self::removeDotSegments($reference->_path);
+ $target->_query = $reference->_query;
} else {
- if ($reference->path == '') {
- $target->path = $this->path;
- if ($reference->query !== false) {
- $target->query = $reference->query;
+ if ($reference->_path == '') {
+ $target->_path = $this->_path;
+ if ($reference->_query !== false) {
+ $target->_query = $reference->_query;
} else {
- $target->query = $this->query;
+ $target->_query = $this->_query;
}
} else {
- if (substr($reference->path, 0, 1) == '/') {
- $target->path = self::removeDotSegments($reference->path);
+ if (substr($reference->_path, 0, 1) == '/') {
+ $target->_path = self::removeDotSegments($reference->_path);
} else {
// Merge paths (RFC 3986, section 5.2.3)
- if ($this->host !== false && $this->path == '') {
- $target->path = '/' . $this->path;
+ if ($this->_host !== false && $this->_path == '') {
+ $target->_path = '/' . $this->_path;
} else {
- $i = strrpos($this->path, '/');
+ $i = strrpos($this->_path, '/');
if ($i !== false) {
- $target->path = substr($this->path, 0, $i + 1);
+ $target->_path = substr($this->_path, 0, $i + 1);
}
- $target->path .= $reference->path;
+ $target->_path .= $reference->_path;
}
- $target->path = self::removeDotSegments($target->path);
+ $target->_path = self::removeDotSegments($target->_path);
}
- $target->query = $reference->query;
+ $target->_query = $reference->_query;
}
$target->setAuthority($this->getAuthority());
}
- $target->scheme = $this->scheme;
+ $target->_scheme = $this->_scheme;
}
- $target->fragment = $reference->fragment;
+ $target->_fragment = $reference->_fragment;
return $target;
}
@@ -677,7 +779,7 @@ class Net_URL2
*
* @return string a path
*/
- private static function removeDotSegments($path)
+ public static function removeDotSegments($path)
{
$output = '';
@@ -685,28 +787,25 @@ class Net_URL2
// method
$j = 0;
while ($path && $j++ < 100) {
- // Step A
if (substr($path, 0, 2) == './') {
+ // Step 2.A
$path = substr($path, 2);
} elseif (substr($path, 0, 3) == '../') {
+ // Step 2.A
$path = substr($path, 3);
-
- // Step B
} elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
+ // Step 2.B
$path = '/' . substr($path, 3);
-
- // Step C
} elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
- $path = '/' . substr($path, 4);
- $i = strrpos($output, '/');
+ // Step 2.C
+ $path = '/' . substr($path, 4);
+ $i = strrpos($output, '/');
$output = $i === false ? '' : substr($output, 0, $i);
-
- // Step D
} elseif ($path == '.' || $path == '..') {
+ // Step 2.D
$path = '';
-
- // Step E
} else {
+ // Step 2.E
$i = strpos($path, '/');
if ($i === 0) {
$i = strpos($path, '/', 1);
@@ -723,6 +822,22 @@ class Net_URL2
}
/**
+ * Percent-encodes all non-alphanumeric characters except these: _ . - ~
+ * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
+ * 5.2.x and earlier.
+ *
+ * @param $raw the string to encode
+ * @return string
+ */
+ public static function urlencode($string)
+ {
+ $encoded = rawurlencode($string);
+ // This is only necessary in PHP < 5.3.
+ $encoded = str_replace('%7E', '~', $encoded);
+ return $encoded;
+ }
+
+ /**
* Returns a Net_URL2 instance representing the canonical URL of the
* currently executing PHP script.
*
@@ -737,13 +852,13 @@ class Net_URL2
// Begin with a relative URL
$url = new self($_SERVER['PHP_SELF']);
- $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
- $url->host = $_SERVER['SERVER_NAME'];
+ $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+ $url->_host = $_SERVER['SERVER_NAME'];
$port = intval($_SERVER['SERVER_PORT']);
- if ($url->scheme == 'http' && $port != 80 ||
- $url->scheme == 'https' && $port != 443) {
+ if ($url->_scheme == 'http' && $port != 80 ||
+ $url->_scheme == 'https' && $port != 443) {
- $url->port = $port;
+ $url->_port = $port;
}
return $url;
}
@@ -773,7 +888,7 @@ class Net_URL2
// Begin with a relative URL
$url = new self($_SERVER['REQUEST_URI']);
- $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+ $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
// Set host and possibly port
$url->setAuthority($_SERVER['HTTP_HOST']);
return $url;
@@ -792,10 +907,10 @@ class Net_URL2
*/
function setOption($optionName, $value)
{
- if (!array_key_exists($optionName, $this->options)) {
+ if (!array_key_exists($optionName, $this->_options)) {
return false;
}
- $this->options[$optionName] = $value;
+ $this->_options[$optionName] = $value;
}
/**
@@ -807,7 +922,7 @@ class Net_URL2
*/
function getOption($optionName)
{
- return isset($this->options[$optionName])
- ? $this->options[$optionName] : false;
+ return isset($this->_options[$optionName])
+ ? $this->_options[$optionName] : false;
}
}
diff --git a/extlib/PEAR.php b/extlib/PEAR.php
index fcefa964a..4c24c6006 100644
--- a/extlib/PEAR.php
+++ b/extlib/PEAR.php
@@ -746,7 +746,7 @@ class PEAR
{
if (!extension_loaded($ext)) {
// if either returns true dl() will produce a FATAL error, stop that
- if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1) || !function_exists('dl')) {
+ if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
return false;
}
if (OS_WINDOWS) {
diff --git a/extlib/Stomp.php b/extlib/Stomp.php
index abd9cba62..b55a4aa6d 100644
--- a/extlib/Stomp.php
+++ b/extlib/Stomp.php
@@ -66,12 +66,13 @@ class Stomp
protected $_sessionId;
protected $_read_timeout_seconds = 60;
protected $_read_timeout_milliseconds = 0;
+ protected $_connect_timeout_seconds = 60;
/**
* Constructor
*
* @param string $brokerUri Broker URL
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function __construct ($brokerUri)
{
@@ -81,7 +82,7 @@ class Stomp
/**
* Initialize connection
*
- * @throws Stomp_Exception
+ * @throws StompException
*/
protected function _init ()
{
@@ -103,14 +104,14 @@ class Stomp
}
} else {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception("Bad Broker URL {$this->_brokerUri}");
+ throw new StompException("Bad Broker URL {$this->_brokerUri}");
}
}
/**
* Process broker URL
*
* @param string $url Broker URL
- * @throws Stomp_Exception
+ * @throws StompException
* @return boolean
*/
protected function _processUrl ($url)
@@ -120,19 +121,19 @@ class Stomp
array_push($this->_hosts, array($parsed['host'] , $parsed['port'] , $parsed['scheme']));
} else {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception("Bad Broker URL $url");
+ throw new StompException("Bad Broker URL $url");
}
}
/**
* Make socket connection to the server
*
- * @throws Stomp_Exception
+ * @throws StompException
*/
protected function _makeConnection ()
{
if (count($this->_hosts) == 0) {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception("No broker defined");
+ throw new StompException("No broker defined");
}
// force disconnect, if previous established connection exists
@@ -141,6 +142,9 @@ class Stomp
$i = $this->_currentHost;
$att = 0;
$connected = false;
+ $connect_errno = null;
+ $connect_errstr = null;
+
while (! $connected && $att ++ < $this->_attempts) {
if (isset($this->_params['randomize']) && $this->_params['randomize'] == 'true') {
$i = rand(0, count($this->_hosts) - 1);
@@ -158,10 +162,10 @@ class Stomp
fclose($this->_socket);
$this->_socket = null;
}
- $this->_socket = @fsockopen($scheme . '://' . $host, $port);
+ $this->_socket = @fsockopen($scheme . '://' . $host, $port, $connect_errno, $connect_errstr, $this->_connect_timeout_seconds);
if (!is_resource($this->_socket) && $att >= $this->_attempts && !array_key_exists($i + 1, $this->_hosts)) {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception("Could not connect to $host:$port ($att/{$this->_attempts})");
+ throw new StompException("Could not connect to $host:$port ($att/{$this->_attempts})");
} else if (is_resource($this->_socket)) {
$connected = true;
$this->_currentHost = $i;
@@ -170,7 +174,7 @@ class Stomp
}
if (! $connected) {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception("Could not connect to a broker");
+ throw new StompException("Could not connect to a broker");
}
}
/**
@@ -179,7 +183,7 @@ class Stomp
* @param string $username
* @param string $password
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function connect ($username = '', $password = '')
{
@@ -194,18 +198,18 @@ class Stomp
if ($this->clientId != null) {
$headers["client-id"] = $this->clientId;
}
- $frame = new Stomp_Frame("CONNECT", $headers);
+ $frame = new StompFrame("CONNECT", $headers);
$this->_writeFrame($frame);
$frame = $this->readFrame();
- if ($frame instanceof Stomp_Frame && $frame->command == 'CONNECTED') {
+ if ($frame instanceof StompFrame && $frame->command == 'CONNECTED') {
$this->_sessionId = $frame->headers["session"];
return true;
} else {
require_once 'Stomp/Exception.php';
- if ($frame instanceof Stomp_Frame) {
- throw new Stomp_Exception("Unexpected command: {$frame->command}", 0, $frame->body);
+ if ($frame instanceof StompFrame) {
+ throw new StompException("Unexpected command: {$frame->command}", 0, $frame->body);
} else {
- throw new Stomp_Exception("Connection not acknowledged");
+ throw new StompException("Connection not acknowledged");
}
}
}
@@ -232,21 +236,21 @@ class Stomp
* Send a message to a destination in the messaging system
*
* @param string $destination Destination queue
- * @param string|Stomp_Frame $msg Message
+ * @param string|StompFrame $msg Message
* @param array $properties
* @param boolean $sync Perform request synchronously
* @return boolean
*/
- public function send ($destination, $msg, $properties = null, $sync = null)
+ public function send ($destination, $msg, $properties = array(), $sync = null)
{
- if ($msg instanceof Stomp_Frame) {
+ if ($msg instanceof StompFrame) {
$msg->headers['destination'] = $destination;
- $msg->headers = array_merge($msg->headers, $properties);
+ if (is_array($properties)) $msg->headers = array_merge($msg->headers, $properties);
$frame = $msg;
} else {
$headers = $properties;
$headers['destination'] = $destination;
- $frame = new Stomp_Frame('SEND', $headers, $msg);
+ $frame = new StompFrame('SEND', $headers, $msg);
}
$this->_prepareReceipt($frame, $sync);
$this->_writeFrame($frame);
@@ -255,10 +259,10 @@ class Stomp
/**
* Prepair frame receipt
*
- * @param Stomp_Frame $frame
+ * @param StompFrame $frame
* @param boolean $sync
*/
- protected function _prepareReceipt (Stomp_Frame $frame, $sync)
+ protected function _prepareReceipt (StompFrame $frame, $sync)
{
$receive = $this->sync;
if ($sync !== null) {
@@ -271,12 +275,12 @@ class Stomp
/**
* Wait for receipt
*
- * @param Stomp_Frame $frame
+ * @param StompFrame $frame
* @param boolean $sync
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
- protected function _waitForReceipt (Stomp_Frame $frame, $sync)
+ protected function _waitForReceipt (StompFrame $frame, $sync)
{
$receive = $this->sync;
@@ -289,19 +293,19 @@ class Stomp
return true;
}
$frame = $this->readFrame();
- if ($frame instanceof Stomp_Frame && $frame->command == 'RECEIPT') {
+ if ($frame instanceof StompFrame && $frame->command == 'RECEIPT') {
if ($frame->headers['receipt-id'] == $id) {
return true;
} else {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception("Unexpected receipt id {$frame->headers['receipt-id']}", 0, $frame->body);
+ throw new StompException("Unexpected receipt id {$frame->headers['receipt-id']}", 0, $frame->body);
}
} else {
require_once 'Stomp/Exception.php';
- if ($frame instanceof Stomp_Frame) {
- throw new Stomp_Exception("Unexpected command {$frame->command}", 0, $frame->body);
+ if ($frame instanceof StompFrame) {
+ throw new StompException("Unexpected command {$frame->command}", 0, $frame->body);
} else {
- throw new Stomp_Exception("Receipt not received");
+ throw new StompException("Receipt not received");
}
}
}
@@ -314,7 +318,7 @@ class Stomp
* @param array $properties
* @param boolean $sync Perform request synchronously
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function subscribe ($destination, $properties = null, $sync = null)
{
@@ -329,7 +333,7 @@ class Stomp
}
}
$headers['destination'] = $destination;
- $frame = new Stomp_Frame('SUBSCRIBE', $headers);
+ $frame = new StompFrame('SUBSCRIBE', $headers);
$this->_prepareReceipt($frame, $sync);
$this->_writeFrame($frame);
if ($this->_waitForReceipt($frame, $sync) == true) {
@@ -346,7 +350,7 @@ class Stomp
* @param array $properties
* @param boolean $sync Perform request synchronously
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function unsubscribe ($destination, $properties = null, $sync = null)
{
@@ -357,7 +361,7 @@ class Stomp
}
}
$headers['destination'] = $destination;
- $frame = new Stomp_Frame('UNSUBSCRIBE', $headers);
+ $frame = new StompFrame('UNSUBSCRIBE', $headers);
$this->_prepareReceipt($frame, $sync);
$this->_writeFrame($frame);
if ($this->_waitForReceipt($frame, $sync) == true) {
@@ -373,7 +377,7 @@ class Stomp
* @param string $transactionId
* @param boolean $sync Perform request synchronously
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function begin ($transactionId = null, $sync = null)
{
@@ -381,7 +385,7 @@ class Stomp
if (isset($transactionId)) {
$headers['transaction'] = $transactionId;
}
- $frame = new Stomp_Frame('BEGIN', $headers);
+ $frame = new StompFrame('BEGIN', $headers);
$this->_prepareReceipt($frame, $sync);
$this->_writeFrame($frame);
return $this->_waitForReceipt($frame, $sync);
@@ -392,7 +396,7 @@ class Stomp
* @param string $transactionId
* @param boolean $sync Perform request synchronously
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function commit ($transactionId = null, $sync = null)
{
@@ -400,7 +404,7 @@ class Stomp
if (isset($transactionId)) {
$headers['transaction'] = $transactionId;
}
- $frame = new Stomp_Frame('COMMIT', $headers);
+ $frame = new StompFrame('COMMIT', $headers);
$this->_prepareReceipt($frame, $sync);
$this->_writeFrame($frame);
return $this->_waitForReceipt($frame, $sync);
@@ -417,7 +421,7 @@ class Stomp
if (isset($transactionId)) {
$headers['transaction'] = $transactionId;
}
- $frame = new Stomp_Frame('ABORT', $headers);
+ $frame = new StompFrame('ABORT', $headers);
$this->_prepareReceipt($frame, $sync);
$this->_writeFrame($frame);
return $this->_waitForReceipt($frame, $sync);
@@ -426,15 +430,19 @@ class Stomp
* Acknowledge consumption of a message from a subscription
* Note: This operation is always asynchronous
*
- * @param string|Stomp_Frame $messageMessage ID
+ * @param string|StompFrame $messageMessage ID
* @param string $transactionId
* @return boolean
- * @throws Stomp_Exception
+ * @throws StompException
*/
public function ack ($message, $transactionId = null)
{
- if ($message instanceof Stomp_Frame) {
- $frame = new Stomp_Frame('ACK', $message->headers);
+ if ($message instanceof StompFrame) {
+ $headers = $message->headers;
+ if (isset($transactionId)) {
+ $headers['transaction'] = $transactionId;
+ }
+ $frame = new StompFrame('ACK', $headers);
$this->_writeFrame($frame);
return true;
} else {
@@ -443,7 +451,7 @@ class Stomp
$headers['transaction'] = $transactionId;
}
$headers['message-id'] = $message;
- $frame = new Stomp_Frame('ACK', $headers);
+ $frame = new StompFrame('ACK', $headers);
$this->_writeFrame($frame);
return true;
}
@@ -461,7 +469,7 @@ class Stomp
}
if (is_resource($this->_socket)) {
- $this->_writeFrame(new Stomp_Frame('DISCONNECT', $headers));
+ $this->_writeFrame(new StompFrame('DISCONNECT', $headers));
fclose($this->_socket);
}
$this->_socket = null;
@@ -474,13 +482,13 @@ class Stomp
/**
* Write frame to server
*
- * @param Stomp_Frame $stompFrame
+ * @param StompFrame $stompFrame
*/
- protected function _writeFrame (Stomp_Frame $stompFrame)
+ protected function _writeFrame (StompFrame $stompFrame)
{
if (!is_resource($this->_socket)) {
require_once 'Stomp/Exception.php';
- throw new Stomp_Exception('Socket connection hasn\'t been established');
+ throw new StompException('Socket connection hasn\'t been established');
}
$data = $stompFrame->__toString();
@@ -504,9 +512,9 @@ class Stomp
}
/**
- * Read responce frame from server
+ * Read response frame from server
*
- * @return Stomp_Frame|Stomp_Message_Map|boolean False when no frame to read
+ * @return StompFrame False when no frame to read
*/
public function readFrame ()
{
@@ -516,15 +524,21 @@ class Stomp
$rb = 1024;
$data = '';
+ $end = false;
+
do {
- $read = fgets($this->_socket, $rb);
+ $read = fread($this->_socket, $rb);
if ($read === false) {
$this->_reconnect();
return $this->readFrame();
}
$data .= $read;
+ if (strpos($data, "\x00") !== false) {
+ $end = true;
+ $data = rtrim($data, "\n");
+ }
$len = strlen($data);
- } while (($len < 2 || ! ($data[$len - 2] == "\x00" && $data[$len - 1] == "\n")));
+ } while ($len < 2 || $end == false);
list ($header, $body) = explode("\n\n", $data, 2);
$header = explode("\n", $header);
@@ -538,13 +552,14 @@ class Stomp
$command = $v;
}
}
- $frame = new Stomp_Frame($command, $headers, trim($body));
- if (isset($frame->headers['amq-msg-type']) && $frame->headers['amq-msg-type'] == 'MapMessage') {
+ $frame = new StompFrame($command, $headers, trim($body));
+ if (isset($frame->headers['transformation']) && $frame->headers['transformation'] == 'jms-map-json') {
require_once 'Stomp/Message/Map.php';
- return new Stomp_Message_Map($frame);
+ return new StompMessageMap($frame);
} else {
return $frame;
}
+ return $frame;
}
/**
@@ -558,10 +573,14 @@ class Stomp
$write = null;
$except = null;
- $has_frame_to_read = stream_select($read, $write, $except, $this->_read_timeout_seconds, $this->_read_timeout_milliseconds);
+ $has_frame_to_read = @stream_select($read, $write, $except, $this->_read_timeout_seconds, $this->_read_timeout_milliseconds);
+
+ if ($has_frame_to_read !== false)
+ $has_frame_to_read = count($read);
+
if ($has_frame_to_read === false) {
- throw new Stomp_Exception('Check failed to determin if the socket is readable');
+ throw new StompException('Check failed to determine if the socket is readable');
} else if ($has_frame_to_read > 0) {
return true;
} else {
diff --git a/extlib/Stomp/Exception.php b/extlib/Stomp/Exception.php
index e6870bc15..8c479de24 100644
--- a/extlib/Stomp/Exception.php
+++ b/extlib/Stomp/Exception.php
@@ -23,10 +23,8 @@
*
*
* @package Stomp
- * @author Michael Caplan <mcaplan@labnet.net>
- * @version $Revision: 23 $
- */
-class Stomp_Exception extends Exception
+ */
+class StompException extends Exception
{
protected $_details;
@@ -53,5 +51,5 @@ class Stomp_Exception extends Exception
{
return $this->_details;
}
-}
+}
?> \ No newline at end of file
diff --git a/extlib/Stomp/Frame.php b/extlib/Stomp/Frame.php
index dc59c1cb7..195e3b627 100644
--- a/extlib/Stomp/Frame.php
+++ b/extlib/Stomp/Frame.php
@@ -1,33 +1,29 @@
-<?php
-/**
- *
- * Copyright 2005-2006 The Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* vim: set expandtab tabstop=3 shiftwidth=3: */
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
/**
- * Stomp Frames are messages that are sent and received on a StompConnection.
+ * Stomp Frames are messages that are sent and received on a stomp connection.
*
* @package Stomp
- * @author Hiram Chirino <hiram@hiramchirino.com>
- * @author Dejan Bosanac <dejan@nighttale.net>
- * @author Michael Caplan <mcaplan@labnet.net>
- * @version $Revision: 36 $
*/
-class Stomp_Frame
+class StompFrame
{
public $command;
public $headers = array();
@@ -54,27 +50,27 @@ class Stomp_Frame
$this->body = $body;
if ($this->command == 'ERROR') {
- require_once 'Stomp/Exception.php';
- throw new Stomp_Exception($this->headers['message'], 0, $this->body);
+ require_once 'Exception.php';
+ throw new StompException($this->headers['message'], 0, $this->body);
+ }
+ }
+
+ /**
+ * Convert frame to transportable string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $data = $this->command . "\n";
+
+ foreach ($this->headers as $name => $value) {
+ $data .= $name . ": " . $value . "\n";
}
- }
-
- /**
- * Convert frame to transportable string
- *
- * @return string
- */
- public function __toString()
- {
- $data = $this->command . "\n";
-
- foreach ($this->headers as $name => $value) {
- $data .= $name . ": " . $value . "\n";
- }
-
- $data .= "\n";
- $data .= $this->body;
- return $data .= "\x00\n";
+
+ $data .= "\n";
+ $data .= $this->body;
+ return $data .= "\x00";
}
}
?> \ No newline at end of file
diff --git a/extlib/Stomp/Message.php b/extlib/Stomp/Message.php
index 6bcad3efd..5a064090c 100644
--- a/extlib/Stomp/Message.php
+++ b/extlib/Stomp/Message.php
@@ -24,10 +24,8 @@ require_once 'Stomp/Frame.php';
* Basic text stomp message
*
* @package Stomp
- * @author Dejan Bosanac <dejan@nighttale.net>
- * @version $Revision: 23 $
*/
-class Stomp_Message extends Stomp_Frame
+class StompMessage extends StompFrame
{
public function __construct ($body, $headers = null)
{
diff --git a/extlib/Stomp/Message/Map.php b/extlib/Stomp/Message/Map.php
index 288456a84..38a32c56c 100644
--- a/extlib/Stomp/Message/Map.php
+++ b/extlib/Stomp/Message/Map.php
@@ -24,30 +24,28 @@ require_once 'Stomp/Message.php';
* Message that contains a set of name-value pairs
*
* @package Stomp
- * @author Dejan Bosanac <dejan@nighttale.net>
- * @version $Revision: 23 $
*/
-class Stomp_Message_Map extends Stomp_Message
+class StompMessageMap extends StompMessage
{
public $map;
/**
* Constructor
*
- * @param Stomp_Frame|string $msg
+ * @param StompFrame|string $msg
* @param array $headers
*/
function __construct ($msg, $headers = null)
{
- if ($msg instanceof Stomp_Frame) {
+ if ($msg instanceof StompFrame) {
$this->_init($msg->command, $msg->headers, $msg->body);
- $this->map = json_decode($msg->body);
+ $this->map = json_decode($msg->body, true);
} else {
$this->_init("SEND", $headers, $msg);
if ($this->headers == null) {
$this->headers = array();
}
- $this->headers['amq-msg-type'] = 'MapMessage';
+ $this->headers['transformation'] = 'jms-map-json';
$this->body = json_encode($msg);
}
}
diff --git a/extlib/facebook/facebook.php b/extlib/facebook/facebook.php
deleted file mode 100644
index 016e8e8e0..000000000
--- a/extlib/facebook/facebook.php
+++ /dev/null
@@ -1,598 +0,0 @@
-<?php
-// Copyright 2004-2009 Facebook. All Rights Reserved.
-//
-// +---------------------------------------------------------------------------+
-// | Facebook Platform PHP5 client |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007 Facebook, Inc. |
-// | All rights reserved. |
-// | |
-// | Redistribution and use in source and binary forms, with or without |
-// | modification, are permitted provided that the following conditions |
-// | are met: |
-// | |
-// | 1. Redistributions of source code must retain the above copyright |
-// | notice, this list of conditions and the following disclaimer. |
-// | 2. Redistributions in binary form must reproduce the above copyright |
-// | notice, this list of conditions and the following disclaimer in the |
-// | documentation and/or other materials provided with the distribution. |
-// | |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// +---------------------------------------------------------------------------+
-// | For help with this library, contact developers-help@facebook.com |
-// +---------------------------------------------------------------------------+
-
-include_once 'facebookapi_php5_restlib.php';
-
-define('FACEBOOK_API_VALIDATION_ERROR', 1);
-class Facebook {
- public $api_client;
- public $api_key;
- public $secret;
- public $generate_session_secret;
- public $session_expires;
-
- public $fb_params;
- public $user;
- public $profile_user;
- public $canvas_user;
- protected $base_domain;
- /*
- * Create a Facebook client like this:
- *
- * $fb = new Facebook(API_KEY, SECRET);
- *
- * This will automatically pull in any parameters, validate them against the
- * session signature, and chuck them in the public $fb_params member variable.
- *
- * @param api_key your Developer API key
- * @param secret your Developer API secret
- * @param generate_session_secret whether to automatically generate a session
- * if the user doesn't have one, but
- * there is an auth token present in the url,
- */
- public function __construct($api_key, $secret, $generate_session_secret=false) {
- $this->api_key = $api_key;
- $this->secret = $secret;
- $this->generate_session_secret = $generate_session_secret;
- $this->api_client = new FacebookRestClient($api_key, $secret, null);
- $this->validate_fb_params();
-
- // Set the default user id for methods that allow the caller to
- // pass an explicit uid instead of using a session key.
- $defaultUser = null;
- if ($this->user) {
- $defaultUser = $this->user;
- } else if ($this->profile_user) {
- $defaultUser = $this->profile_user;
- } else if ($this->canvas_user) {
- $defaultUser = $this->canvas_user;
- }
-
- $this->api_client->set_user($defaultUser);
-
-
- if (isset($this->fb_params['friends'])) {
- $this->api_client->friends_list = explode(',', $this->fb_params['friends']);
- }
- if (isset($this->fb_params['added'])) {
- $this->api_client->added = $this->fb_params['added'];
- }
- if (isset($this->fb_params['canvas_user'])) {
- $this->api_client->canvas_user = $this->fb_params['canvas_user'];
- }
- }
-
- /*
- * Validates that the parameters passed in were sent from Facebook. It does so
- * by validating that the signature matches one that could only be generated
- * by using your application's secret key.
- *
- * Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE,
- * in that order. $_POST and $_GET are always more up-to-date than cookies,
- * so we prefer those if they are available.
- *
- * For nitty-gritty details of when each of these is used, check out
- * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
- *
- * @param bool resolve_auth_token convert an auth token into a session
- */
- public function validate_fb_params($resolve_auth_token=true) {
- $this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
-
- // note that with preload FQL, it's possible to receive POST params in
- // addition to GET, so use a different prefix to differentiate them
- if (!$this->fb_params) {
- $fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
- $fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
- $this->fb_params = array_merge($fb_params, $fb_post_params);
- }
-
- // Okay, something came in via POST or GET
- if ($this->fb_params) {
- $user = isset($this->fb_params['user']) ?
- $this->fb_params['user'] : null;
- $this->profile_user = isset($this->fb_params['profile_user']) ?
- $this->fb_params['profile_user'] : null;
- $this->canvas_user = isset($this->fb_params['canvas_user']) ?
- $this->fb_params['canvas_user'] : null;
- $this->base_domain = isset($this->fb_params['base_domain']) ?
- $this->fb_params['base_domain'] : null;
-
- if (isset($this->fb_params['session_key'])) {
- $session_key = $this->fb_params['session_key'];
- } else if (isset($this->fb_params['profile_session_key'])) {
- $session_key = $this->fb_params['profile_session_key'];
- } else {
- $session_key = null;
- }
- $expires = isset($this->fb_params['expires']) ?
- $this->fb_params['expires'] : null;
- $this->set_user($user,
- $session_key,
- $expires);
- }
- // if no Facebook parameters were found in the GET or POST variables,
- // then fall back to cookies, which may have cached user information
- // Cookies are also used to receive session data via the Javascript API
- else if ($cookies =
- $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
-
- $base_domain_cookie = 'base_domain_' . $this->api_key;
- if (isset($_COOKIE[$base_domain_cookie])) {
- $this->base_domain = $_COOKIE[$base_domain_cookie];
- }
-
- // use $api_key . '_' as a prefix for the cookies in case there are
- // multiple facebook clients on the same domain.
- $expires = isset($cookies['expires']) ? $cookies['expires'] : null;
- $this->set_user($cookies['user'],
- $cookies['session_key'],
- $expires);
- }
- // finally, if we received no parameters, but the 'auth_token' GET var
- // is present, then we are in the middle of auth handshake,
- // so go ahead and create the session
- else if ($resolve_auth_token && isset($_GET['auth_token']) &&
- $session = $this->do_get_session($_GET['auth_token'])) {
- if ($this->generate_session_secret &&
- !empty($session['secret'])) {
- $session_secret = $session['secret'];
- }
-
- if (isset($session['base_domain'])) {
- $this->base_domain = $session['base_domain'];
- }
-
- $this->set_user($session['uid'],
- $session['session_key'],
- $session['expires'],
- isset($session_secret) ? $session_secret : null);
- }
-
- return !empty($this->fb_params);
- }
-
- // Store a temporary session secret for the current session
- // for use with the JS client library
- public function promote_session() {
- try {
- $session_secret = $this->api_client->auth_promoteSession();
- if (!$this->in_fb_canvas()) {
- $this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret);
- }
- return $session_secret;
- } catch (FacebookRestClientException $e) {
- // API_EC_PARAM means we don't have a logged in user, otherwise who
- // knows what it means, so just throw it.
- if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
- throw $e;
- }
- }
- }
-
- public function do_get_session($auth_token) {
- try {
- return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret);
- } catch (FacebookRestClientException $e) {
- // API_EC_PARAM means we don't have a logged in user, otherwise who
- // knows what it means, so just throw it.
- if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
- throw $e;
- }
- }
- }
-
- // Invalidate the session currently being used, and clear any state associated
- // with it. Note that the user will still remain logged into Facebook.
- public function expire_session() {
- if ($this->api_client->auth_expireSession()) {
- $this->clear_cookie_state();
- return true;
- } else {
- return false;
- }
- }
-
- /** Logs the user out of all temporary application sessions as well as their
- * Facebook session. Note this will only work if the user has a valid current
- * session with the application.
- *
- * @param string $next URL to redirect to upon logging out
- *
- */
- public function logout($next) {
- $logout_url = $this->get_logout_url($next);
-
- // Clear any stored state
- $this->clear_cookie_state();
-
- $this->redirect($logout_url);
- }
-
- /**
- * Clears any persistent state stored about the user, including
- * cookies and information related to the current session in the
- * client.
- *
- */
- public function clear_cookie_state() {
- if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
- $cookies = array('user', 'session_key', 'expires', 'ss');
- foreach ($cookies as $name) {
- setcookie($this->api_key . '_' . $name, false, time() - 3600);
- unset($_COOKIE[$this->api_key . '_' . $name]);
- }
- setcookie($this->api_key, false, time() - 3600);
- unset($_COOKIE[$this->api_key]);
- }
-
- // now, clear the rest of the stored state
- $this->user = 0;
- $this->api_client->session_key = 0;
- }
-
- public function redirect($url) {
- if ($this->in_fb_canvas()) {
- echo '<fb:redirect url="' . $url . '"/>';
- } else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) {
- // make sure facebook.com url's load in the full frame so that we don't
- // get a frame within a frame.
- echo "<script type=\"text/javascript\">\ntop.location.href = \"$url\";\n</script>";
- } else {
- header('Location: ' . $url);
- }
- exit;
- }
-
- public function in_frame() {
- return isset($this->fb_params['in_canvas'])
- || isset($this->fb_params['in_iframe']);
- }
- public function in_fb_canvas() {
- return isset($this->fb_params['in_canvas']);
- }
-
- public function get_loggedin_user() {
- return $this->user;
- }
-
- public function get_canvas_user() {
- return $this->canvas_user;
- }
-
- public function get_profile_user() {
- return $this->profile_user;
- }
-
- public static function current_url() {
- return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
- }
-
- // require_add and require_install have been removed.
- // see http://developer.facebook.com/news.php?blog=1&story=116 for more details
- public function require_login() {
- if ($user = $this->get_loggedin_user()) {
- return $user;
- }
- $this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
- }
-
- public function require_frame() {
- if (!$this->in_frame()) {
- $this->redirect($this->get_login_url(self::current_url(), true));
- }
- }
-
- public static function get_facebook_url($subdomain='www') {
- return 'http://' . $subdomain . '.facebook.com';
- }
-
- public function get_install_url($next=null) {
- // this was renamed, keeping for compatibility's sake
- return $this->get_add_url($next);
- }
-
- public function get_add_url($next=null) {
- $page = self::get_facebook_url().'/add.php';
- $params = array('api_key' => $this->api_key);
-
- if ($next) {
- $params['next'] = $next;
- }
-
- return $page . '?' . http_build_query($params);
- }
-
- public function get_login_url($next, $canvas) {
- $page = self::get_facebook_url().'/login.php';
- $params = array('api_key' => $this->api_key,
- 'v' => '1.0');
-
- if ($next) {
- $params['next'] = $next;
- }
- if ($canvas) {
- $params['canvas'] = '1';
- }
-
- return $page . '?' . http_build_query($params);
- }
-
- public function get_logout_url($next) {
- $page = self::get_facebook_url().'/logout.php';
- $params = array('app_key' => $this->api_key,
- 'session_key' => $this->api_client->session_key);
-
- if ($next) {
- $params['connect_next'] = 1;
- $params['next'] = $next;
- }
-
- return $page . '?' . http_build_query($params);
- }
-
- public function set_user($user, $session_key, $expires=null, $session_secret=null) {
- if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user'])
- || $_COOKIE[$this->api_key . '_user'] != $user)) {
- $this->set_cookies($user, $session_key, $expires, $session_secret);
- }
- $this->user = $user;
- $this->api_client->session_key = $session_key;
- $this->session_expires = $expires;
- }
-
- public function set_cookies($user, $session_key, $expires=null, $session_secret=null) {
- $cookies = array();
- $cookies['user'] = $user;
- $cookies['session_key'] = $session_key;
- if ($expires != null) {
- $cookies['expires'] = $expires;
- }
- if ($session_secret != null) {
- $cookies['ss'] = $session_secret;
- }
-
- foreach ($cookies as $name => $val) {
- setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain);
- $_COOKIE[$this->api_key . '_' . $name] = $val;
- }
- $sig = self::generate_sig($cookies, $this->secret);
- setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain);
- $_COOKIE[$this->api_key] = $sig;
-
- if ($this->base_domain != null) {
- $base_domain_cookie = 'base_domain_' . $this->api_key;
- setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain);
- $_COOKIE[$base_domain_cookie] = $this->base_domain;
- }
- }
-
- /**
- * Tries to undo the badness of magic quotes as best we can
- * @param string $val Should come directly from $_GET, $_POST, etc.
- * @return string val without added slashes
- */
- public static function no_magic_quotes($val) {
- if (get_magic_quotes_gpc()) {
- return stripslashes($val);
- } else {
- return $val;
- }
- }
-
- /*
- * Get the signed parameters that were sent from Facebook. Validates the set
- * of parameters against the included signature.
- *
- * Since Facebook sends data to your callback URL via unsecured means, the
- * signature is the only way to make sure that the data actually came from
- * Facebook. So if an app receives a request at the callback URL, it should
- * always verify the signature that comes with against your own secret key.
- * Otherwise, it's possible for someone to spoof a request by
- * pretending to be someone else, i.e.:
- * www.your-callback-url.com/?fb_user=10101
- *
- * This is done automatically by verify_fb_params.
- *
- * @param assoc $params a full array of external parameters.
- * presumed $_GET, $_POST, or $_COOKIE
- * @param int $timeout number of seconds that the args are good for.
- * Specifically good for forcing cookies to expire.
- * @param string $namespace prefix string for the set of parameters we want
- * to verify. i.e., fb_sig or fb_post_sig
- *
- * @return assoc the subset of parameters containing the given prefix,
- * and also matching the signature associated with them.
- * OR an empty array if the params do not validate
- */
- public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') {
- $prefix = $namespace . '_';
- $prefix_len = strlen($prefix);
- $fb_params = array();
- if (empty($params)) {
- return array();
- }
-
- foreach ($params as $name => $val) {
- // pull out only those parameters that match the prefix
- // note that the signature itself ($params[$namespace]) is not in the list
- if (strpos($name, $prefix) === 0) {
- $fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val);
- }
- }
-
- // validate that the request hasn't expired. this is most likely
- // for params that come from $_COOKIE
- if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) {
- return array();
- }
-
- // validate that the params match the signature
- $signature = isset($params[$namespace]) ? $params[$namespace] : null;
- if (!$signature || (!$this->verify_signature($fb_params, $signature))) {
- return array();
- }
- return $fb_params;
- }
-
- /**
- * Validates the account that a user was trying to set up an
- * independent account through Facebook Connect.
- *
- * @param user The user attempting to set up an independent account.
- * @param hash The hash passed to the reclamation URL used.
- * @return bool True if the user is the one that selected the
- * reclamation link.
- */
- public function verify_account_reclamation($user, $hash) {
- return $hash == md5($user . $this->secret);
- }
-
- /**
- * Validates that a given set of parameters match their signature.
- * Parameters all match a given input prefix, such as "fb_sig".
- *
- * @param $fb_params an array of all Facebook-sent parameters,
- * not including the signature itself
- * @param $expected_sig the expected result to check against
- */
- public function verify_signature($fb_params, $expected_sig) {
- return self::generate_sig($fb_params, $this->secret) == $expected_sig;
- }
-
- /**
- * Validate the given signed public session data structure with
- * public key of the app that
- * the session proof belongs to.
- *
- * @param $signed_data the session info that is passed by another app
- * @param string $public_key Optional public key of the app. If this
- * is not passed, function will make an API call to get it.
- * return true if the session proof passed verification.
- */
- public function verify_signed_public_session_data($signed_data,
- $public_key = null) {
-
- // If public key is not already provided, we need to get it through API
- if (!$public_key) {
- $public_key = $this->api_client->auth_getAppPublicKey(
- $signed_data['api_key']);
- }
-
- // Create data to verify
- $data_to_serialize = $signed_data;
- unset($data_to_serialize['sig']);
- $serialized_data = implode('_', $data_to_serialize);
-
- // Decode signature
- $signature = base64_decode($signed_data['sig']);
- $result = openssl_verify($serialized_data, $signature, $public_key,
- OPENSSL_ALGO_SHA1);
- return $result == 1;
- }
-
- /*
- * Generate a signature using the application secret key.
- *
- * The only two entities that know your secret key are you and Facebook,
- * according to the Terms of Service. Since nobody else can generate
- * the signature, you can rely on it to verify that the information
- * came from Facebook.
- *
- * @param $params_array an array of all Facebook-sent parameters,
- * NOT INCLUDING the signature itself
- * @param $secret your app's secret key
- *
- * @return a hash to be checked against the signature provided by Facebook
- */
- public static function generate_sig($params_array, $secret) {
- $str = '';
-
- ksort($params_array);
- // Note: make sure that the signature parameter is not already included in
- // $params_array.
- foreach ($params_array as $k=>$v) {
- $str .= "$k=$v";
- }
- $str .= $secret;
-
- return md5($str);
- }
-
- public function encode_validationError($summary, $message) {
- return json_encode(
- array('errorCode' => FACEBOOK_API_VALIDATION_ERROR,
- 'errorTitle' => $summary,
- 'errorMessage' => $message));
- }
-
- public function encode_multiFeedStory($feed, $next) {
- return json_encode(
- array('method' => 'multiFeedStory',
- 'content' =>
- array('next' => $next,
- 'feed' => $feed)));
- }
-
- public function encode_feedStory($feed, $next) {
- return json_encode(
- array('method' => 'feedStory',
- 'content' =>
- array('next' => $next,
- 'feed' => $feed)));
- }
-
- public function create_templatizedFeedStory($title_template, $title_data=array(),
- $body_template='', $body_data = array(), $body_general=null,
- $image_1=null, $image_1_link=null,
- $image_2=null, $image_2_link=null,
- $image_3=null, $image_3_link=null,
- $image_4=null, $image_4_link=null) {
- return array('title_template'=> $title_template,
- 'title_data' => $title_data,
- 'body_template'=> $body_template,
- 'body_data' => $body_data,
- 'body_general' => $body_general,
- 'image_1' => $image_1,
- 'image_1_link' => $image_1_link,
- 'image_2' => $image_2,
- 'image_2_link' => $image_2_link,
- 'image_3' => $image_3,
- 'image_3_link' => $image_3_link,
- 'image_4' => $image_4,
- 'image_4_link' => $image_4_link);
- }
-
-
-}
-
diff --git a/extlib/facebook/facebook_desktop.php b/extlib/facebook/facebook_desktop.php
deleted file mode 100644
index e79a2ca34..000000000
--- a/extlib/facebook/facebook_desktop.php
+++ /dev/null
@@ -1,104 +0,0 @@
-<?php
-// Copyright 2004-2009 Facebook. All Rights Reserved.
-//
-// +---------------------------------------------------------------------------+
-// | Facebook Platform PHP5 client |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007 Facebook, Inc. |
-// | All rights reserved. |
-// | |
-// | Redistribution and use in source and binary forms, with or without |
-// | modification, are permitted provided that the following conditions |
-// | are met: |
-// | |
-// | 1. Redistributions of source code must retain the above copyright |
-// | notice, this list of conditions and the following disclaimer. |
-// | 2. Redistributions in binary form must reproduce the above copyright |
-// | notice, this list of conditions and the following disclaimer in the |
-// | documentation and/or other materials provided with the distribution. |
-// | |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// +---------------------------------------------------------------------------+
-// | For help with this library, contact developers-help@facebook.com |
-// +---------------------------------------------------------------------------+
-//
-
-/**
- * This class extends and modifies the "Facebook" class to better
- * suit desktop apps.
- */
-class FacebookDesktop extends Facebook {
- // the application secret, which differs from the session secret
- public $app_secret;
- public $verify_sig;
-
- public function __construct($api_key, $secret) {
- $this->app_secret = $secret;
- $this->verify_sig = false;
- parent::__construct($api_key, $secret);
- }
-
- public function do_get_session($auth_token) {
- $this->api_client->secret = $this->app_secret;
- $this->api_client->session_key = null;
- $session_info = parent::do_get_session($auth_token);
- if (!empty($session_info['secret'])) {
- // store the session secret
- $this->set_session_secret($session_info['secret']);
- }
- return $session_info;
- }
-
- public function set_session_secret($session_secret) {
- $this->secret = $session_secret;
- $this->api_client->secret = $session_secret;
- }
-
- public function require_login() {
- if ($this->get_loggedin_user()) {
- try {
- // try a session-based API call to ensure that we have the correct
- // session secret
- $user = $this->api_client->users_getLoggedInUser();
-
- // now that we have a valid session secret, verify the signature
- $this->verify_sig = true;
- if ($this->validate_fb_params(false)) {
- return $user;
- } else {
- // validation failed
- return null;
- }
- } catch (FacebookRestClientException $ex) {
- if (isset($_GET['auth_token'])) {
- // if we have an auth_token, use it to establish a session
- $session_info = $this->do_get_session($_GET['auth_token']);
- if ($session_info) {
- return $session_info['uid'];
- }
- }
- }
- }
- // if we get here, we need to redirect the user to log in
- $this->redirect($this->get_login_url(self::current_url(), $this->in_fb_canvas()));
- }
-
- public function verify_signature($fb_params, $expected_sig) {
- // we don't want to verify the signature until we have a valid
- // session secret
- if ($this->verify_sig) {
- return parent::verify_signature($fb_params, $expected_sig);
- } else {
- return true;
- }
- }
-}
diff --git a/extlib/facebook/facebookapi_php5_restlib.php b/extlib/facebook/facebookapi_php5_restlib.php
deleted file mode 100755
index 55cb7fb86..000000000
--- a/extlib/facebook/facebookapi_php5_restlib.php
+++ /dev/null
@@ -1,3618 +0,0 @@
-<?php
-// Copyright 2004-2009 Facebook. All Rights Reserved.
-//
-// +---------------------------------------------------------------------------+
-// | Facebook Platform PHP5 client |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007-2009 Facebook, Inc. |
-// | All rights reserved. |
-// | |
-// | Redistribution and use in source and binary forms, with or without |
-// | modification, are permitted provided that the following conditions |
-// | are met: |
-// | |
-// | 1. Redistributions of source code must retain the above copyright |
-// | notice, this list of conditions and the following disclaimer. |
-// | 2. Redistributions in binary form must reproduce the above copyright |
-// | notice, this list of conditions and the following disclaimer in the |
-// | documentation and/or other materials provided with the distribution. |
-// | |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// +---------------------------------------------------------------------------+
-// | For help with this library, contact developers-help@facebook.com |
-// +---------------------------------------------------------------------------+
-//
-
-include_once 'jsonwrapper/jsonwrapper.php';
-
-class FacebookRestClient {
- public $secret;
- public $session_key;
- public $api_key;
- // to save making the friends.get api call, this will get prepopulated on
- // canvas pages
- public $friends_list;
- public $user;
- // to save making the pages.isAppAdded api call, this will get prepopulated
- // on canvas pages
- public $added;
- public $is_user;
- // we don't pass friends list to iframes, but we want to make
- // friends_get really simple in the canvas_user (non-logged in) case.
- // So we use the canvas_user as default arg to friends_get
- public $canvas_user;
- public $batch_mode;
- private $batch_queue;
- private $pending_batch;
- private $call_as_apikey;
- private $use_curl_if_available;
- private $format = null;
-
- const BATCH_MODE_DEFAULT = 0;
- const BATCH_MODE_SERVER_PARALLEL = 0;
- const BATCH_MODE_SERIAL_ONLY = 2;
-
- /**
- * Create the client.
- * @param string $session_key if you haven't gotten a session key yet, leave
- * this as null and then set it later by just
- * directly accessing the $session_key member
- * variable.
- */
- public function __construct($api_key, $secret, $session_key=null) {
- $this->secret = $secret;
- $this->session_key = $session_key;
- $this->api_key = $api_key;
- $this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT;
- $this->last_call_id = 0;
- $this->call_as_apikey = '';
- $this->use_curl_if_available = true;
- $this->server_addr = Facebook::get_facebook_url('api') . '/restserver.php';
-
- if (!empty($GLOBALS['facebook_config']['debug'])) {
- $this->cur_id = 0;
- ?>
-<script type="text/javascript">
-var types = ['params', 'xml', 'php', 'sxml'];
-function getStyle(elem, style) {
- if (elem.getStyle) {
- return elem.getStyle(style);
- } else {
- return elem.style[style];
- }
-}
-function setStyle(elem, style, value) {
- if (elem.setStyle) {
- elem.setStyle(style, value);
- } else {
- elem.style[style] = value;
- }
-}
-function toggleDisplay(id, type) {
- for (var i = 0; i < types.length; i++) {
- var t = types[i];
- var pre = document.getElementById(t + id);
- if (pre) {
- if (t != type || getStyle(pre, 'display') == 'block') {
- setStyle(pre, 'display', 'none');
- } else {
- setStyle(pre, 'display', 'block');
- }
- }
- }
- return false;
-}
-</script>
-<?php
- }
- }
-
- /**
- * Set the default user id for methods that allow the caller
- * to pass an uid parameter to identify the target user
- * instead of a session key. This currently applies to
- * the user preferences methods.
- *
- * @param $uid int the user id
- */
- public function set_user($uid) {
- $this->user = $uid;
- }
-
- /**
- * Normally, if the cURL library/PHP extension is available, it is used for
- * HTTP transactions. This allows that behavior to be overridden, falling
- * back to a vanilla-PHP implementation even if cURL is installed.
- *
- * @param $use_curl_if_available bool whether or not to use cURL if available
- */
- public function set_use_curl_if_available($use_curl_if_available) {
- $this->use_curl_if_available = $use_curl_if_available;
- }
-
- /**
- * Start a batch operation.
- */
- public function begin_batch() {
- if ($this->pending_batch()) {
- $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- $this->batch_queue = array();
- $this->pending_batch = true;
- }
-
- /*
- * End current batch operation
- */
- public function end_batch() {
- if (!$this->pending_batch()) {
- $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- $this->pending_batch = false;
-
- $this->execute_server_side_batch();
- $this->batch_queue = null;
- }
-
- /**
- * are we currently queueing up calls for a batch?
- */
- public function pending_batch() {
- return $this->pending_batch;
- }
-
- private function execute_server_side_batch() {
- $item_count = count($this->batch_queue);
- $method_feed = array();
- foreach ($this->batch_queue as $batch_item) {
- $method = $batch_item['m'];
- $params = $batch_item['p'];
- list($get, $post) = $this->finalize_params($method, $params);
- $method_feed[] = $this->create_url_string(array_merge($post, $get));
- }
-
- $serial_only =
- ($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
-
- $params = array('method_feed' => json_encode($method_feed),
- 'serial_only' => $serial_only,
- 'format' => $this->format);
- $result = $this->call_method('facebook.batch.run', $params);
-
- if (is_array($result) && isset($result['error_code'])) {
- throw new FacebookRestClientException($result['error_msg'],
- $result['error_code']);
- }
-
- for ($i = 0; $i < $item_count; $i++) {
- $batch_item = $this->batch_queue[$i];
- $batch_item['p']['format'] = $this->format;
- $batch_item_result = $this->convert_result($result[$i],
- $batch_item['m'],
- $batch_item['p']);
-
- if (is_array($batch_item_result) &&
- isset($batch_item_result['error_code'])) {
- throw new FacebookRestClientException($batch_item_result['error_msg'],
- $batch_item_result['error_code']);
- }
- $batch_item['r'] = $batch_item_result;
- }
- }
-
- public function begin_permissions_mode($permissions_apikey) {
- $this->call_as_apikey = $permissions_apikey;
- }
-
- public function end_permissions_mode() {
- $this->call_as_apikey = '';
- }
-
-
- /*
- * If a page is loaded via HTTPS, then all images and static
- * resources need to be printed with HTTPS urls to avoid
- * mixed content warnings. If your page loads with an HTTPS
- * url, then call set_use_ssl_resources to retrieve the correct
- * urls.
- */
- public function set_use_ssl_resources($is_ssl = true) {
- $this->use_ssl_resources = $is_ssl;
- }
-
- /**
- * Returns public information for an application (as shown in the application
- * directory) by either application ID, API key, or canvas page name.
- *
- * @param int $application_id (Optional) app id
- * @param string $application_api_key (Optional) api key
- * @param string $application_canvas_name (Optional) canvas name
- *
- * Exactly one argument must be specified, otherwise it is an error.
- *
- * @return array An array of public information about the application.
- */
- public function application_getPublicInfo($application_id=null,
- $application_api_key=null,
- $application_canvas_name=null) {
- return $this->call_method('facebook.application.getPublicInfo',
- array('application_id' => $application_id,
- 'application_api_key' => $application_api_key,
- 'application_canvas_name' => $application_canvas_name));
- }
-
- /**
- * Creates an authentication token to be used as part of the desktop login
- * flow. For more information, please see
- * http://wiki.developers.facebook.com/index.php/Auth.createToken.
- *
- * @return string An authentication token.
- */
- public function auth_createToken() {
- return $this->call_method('facebook.auth.createToken');
- }
-
- /**
- * Returns the session information available after current user logs in.
- *
- * @param string $auth_token the token returned by
- * auth_createToken or passed back to
- * your callback_url.
- * @param bool $generate_session_secret whether the session returned should
- * include a session secret
- *
- * @return array An assoc array containing session_key, uid
- */
- public function auth_getSession($auth_token, $generate_session_secret=false) {
- if (!$this->pending_batch()) {
- $result = $this->call_method('facebook.auth.getSession',
- array('auth_token' => $auth_token,
- 'generate_session_secret' => $generate_session_secret));
- $this->session_key = $result['session_key'];
-
- if (!empty($result['secret']) && !$generate_session_secret) {
- // desktop apps have a special secret
- $this->secret = $result['secret'];
- }
- return $result;
- }
- }
-
- /**
- * Generates a session-specific secret. This is for integration with
- * client-side API calls, such as the JS library.
- *
- * @return array A session secret for the current promoted session
- *
- * @error API_EC_PARAM_SESSION_KEY
- * API_EC_PARAM_UNKNOWN
- */
- public function auth_promoteSession() {
- return $this->call_method('facebook.auth.promoteSession');
- }
-
- /**
- * Expires the session that is currently being used. If this call is
- * successful, no further calls to the API (which require a session) can be
- * made until a valid session is created.
- *
- * @return bool true if session expiration was successful, false otherwise
- */
- public function auth_expireSession() {
- return $this->call_method('facebook.auth.expireSession');
- }
-
- /**
- * Revokes the given extended permission that the user granted at some
- * prior time (for instance, offline_access or email). If no user is
- * provided, it will be revoked for the user of the current session.
- *
- * @param string $perm The permission to revoke
- * @param int $uid The user for whom to revoke the permission.
- */
- public function auth_revokeExtendedPermission($perm, $uid=null) {
- return $this->call_method('facebook.auth.revokeExtendedPermission',
- array('perm' => $perm, 'uid' => $uid));
- }
-
- /**
- * Revokes the user's agreement to the Facebook Terms of Service for your
- * application. If you call this method for one of your users, you will no
- * longer be able to make API requests on their behalf until they again
- * authorize your application. Use with care. Note that if this method is
- * called without a user parameter, then it will revoke access for the
- * current session's user.
- *
- * @param int $uid (Optional) User to revoke
- *
- * @return bool true if revocation succeeds, false otherwise
- */
- public function auth_revokeAuthorization($uid=null) {
- return $this->call_method('facebook.auth.revokeAuthorization',
- array('uid' => $uid));
- }
-
- /**
- * Get public key that is needed to verify digital signature
- * an app may pass to other apps. The public key is only used by
- * other apps for verification purposes.
- * @param string API key of an app
- * @return string The public key for the app.
- */
- public function auth_getAppPublicKey($target_app_key) {
- return $this->call_method('facebook.auth.getAppPublicKey',
- array('target_app_key' => $target_app_key));
- }
-
- /**
- * Get a structure that can be passed to another app
- * as proof of session. The other app can verify it using public
- * key of this app.
- *
- * @return signed public session data structure.
- */
- public function auth_getSignedPublicSessionData() {
- return $this->call_method('facebook.auth.getSignedPublicSessionData',
- array());
- }
-
- /**
- * Returns the number of unconnected friends that exist in this application.
- * This number is determined based on the accounts registered through
- * connect.registerUsers() (see below).
- */
- public function connect_getUnconnectedFriendsCount() {
- return $this->call_method('facebook.connect.getUnconnectedFriendsCount',
- array());
- }
-
- /**
- * This method is used to create an association between an external user
- * account and a Facebook user account, as per Facebook Connect.
- *
- * This method takes an array of account data, including a required email_hash
- * and optional account data. For each connected account, if the user exists,
- * the information is added to the set of the user's connected accounts.
- * If the user has already authorized the site, the connected account is added
- * in the confirmed state. If the user has not yet authorized the site, the
- * connected account is added in the pending state.
- *
- * This is designed to help Facebook Connect recognize when two Facebook
- * friends are both members of a external site, but perhaps are not aware of
- * it. The Connect dialog (see fb:connect-form) is used when friends can be
- * identified through these email hashes. See the following url for details:
- *
- * http://wiki.developers.facebook.com/index.php/Connect.registerUsers
- *
- * @param mixed $accounts A (JSON-encoded) array of arrays, where each array
- * has three properties:
- * 'email_hash' (req) - public email hash of account
- * 'account_id' (opt) - remote account id;
- * 'account_url' (opt) - url to remote account;
- *
- * @return array The list of email hashes for the successfully registered
- * accounts.
- */
- public function connect_registerUsers($accounts) {
- return $this->call_method('facebook.connect.registerUsers',
- array('accounts' => $accounts));
- }
-
- /**
- * Unregisters a set of accounts registered using connect.registerUsers.
- *
- * @param array $email_hashes The (JSON-encoded) list of email hashes to be
- * unregistered.
- *
- * @return array The list of email hashes which have been successfully
- * unregistered.
- */
- public function connect_unregisterUsers($email_hashes) {
- return $this->call_method('facebook.connect.unregisterUsers',
- array('email_hashes' => $email_hashes));
- }
-
- /**
- * Returns events according to the filters specified.
- *
- * @param int $uid (Optional) User associated with events. A null
- * parameter will default to the session user.
- * @param array/string $eids (Optional) Filter by these event
- * ids. A null parameter will get all events for
- * the user. (A csv list will work but is deprecated)
- * @param int $start_time (Optional) Filter with this unix time as lower
- * bound. A null or zero parameter indicates no
- * lower bound.
- * @param int $end_time (Optional) Filter with this UTC as upper bound.
- * A null or zero parameter indicates no upper
- * bound.
- * @param string $rsvp_status (Optional) Only show events where the given uid
- * has this rsvp status. This only works if you
- * have specified a value for $uid. Values are as
- * in events.getMembers. Null indicates to ignore
- * rsvp status when filtering.
- *
- * @return array The events matching the query.
- */
- public function &events_get($uid=null,
- $eids=null,
- $start_time=null,
- $end_time=null,
- $rsvp_status=null) {
- return $this->call_method('facebook.events.get',
- array('uid' => $uid,
- 'eids' => $eids,
- 'start_time' => $start_time,
- 'end_time' => $end_time,
- 'rsvp_status' => $rsvp_status));
- }
-
- /**
- * Returns membership list data associated with an event.
- *
- * @param int $eid event id
- *
- * @return array An assoc array of four membership lists, with keys
- * 'attending', 'unsure', 'declined', and 'not_replied'
- */
- public function &events_getMembers($eid) {
- return $this->call_method('facebook.events.getMembers',
- array('eid' => $eid));
- }
-
- /**
- * RSVPs the current user to this event.
- *
- * @param int $eid event id
- * @param string $rsvp_status 'attending', 'unsure', or 'declined'
- *
- * @return bool true if successful
- */
- public function &events_rsvp($eid, $rsvp_status) {
- return $this->call_method('facebook.events.rsvp',
- array(
- 'eid' => $eid,
- 'rsvp_status' => $rsvp_status));
- }
-
- /**
- * Cancels an event. Only works for events where application is the admin.
- *
- * @param int $eid event id
- * @param string $cancel_message (Optional) message to send to members of
- * the event about why it is cancelled
- *
- * @return bool true if successful
- */
- public function &events_cancel($eid, $cancel_message='') {
- return $this->call_method('facebook.events.cancel',
- array('eid' => $eid,
- 'cancel_message' => $cancel_message));
- }
-
- /**
- * Creates an event on behalf of the user is there is a session, otherwise on
- * behalf of app. Successful creation guarantees app will be admin.
- *
- * @param assoc array $event_info json encoded event information
- * @param string $file (Optional) filename of picture to set
- *
- * @return int event id
- */
- public function events_create($event_info, $file = null) {
- if ($file) {
- return $this->call_upload_method('facebook.events.create',
- array('event_info' => $event_info),
- $file,
- Facebook::get_facebook_url('api-photo') . '/restserver.php');
- } else {
- return $this->call_method('facebook.events.create',
- array('event_info' => $event_info));
- }
- }
-
- /**
- * Edits an existing event. Only works for events where application is admin.
- *
- * @param int $eid event id
- * @param assoc array $event_info json encoded event information
- * @param string $file (Optional) filename of new picture to set
- *
- * @return bool true if successful
- */
- public function events_edit($eid, $event_info, $file = null) {
- if ($file) {
- return $this->call_upload_method('facebook.events.edit',
- array('eid' => $eid, 'event_info' => $event_info),
- $file,
- Facebook::get_facebook_url('api-photo') . '/restserver.php');
- } else {
- return $this->call_method('facebook.events.edit',
- array('eid' => $eid,
- 'event_info' => $event_info));
- }
- }
-
- /**
- * Fetches and re-caches the image stored at the given URL, for use in images
- * published to non-canvas pages via the API (for example, to user profiles
- * via profile.setFBML, or to News Feed via feed.publishUserAction).
- *
- * @param string $url The absolute URL from which to refresh the image.
- *
- * @return bool true on success
- */
- public function &fbml_refreshImgSrc($url) {
- return $this->call_method('facebook.fbml.refreshImgSrc',
- array('url' => $url));
- }
-
- /**
- * Fetches and re-caches the content stored at the given URL, for use in an
- * fb:ref FBML tag.
- *
- * @param string $url The absolute URL from which to fetch content. This URL
- * should be used in a fb:ref FBML tag.
- *
- * @return bool true on success
- */
- public function &fbml_refreshRefUrl($url) {
- return $this->call_method('facebook.fbml.refreshRefUrl',
- array('url' => $url));
- }
-
- /**
- * Lets you insert text strings in their native language into the Facebook
- * Translations database so they can be translated.
- *
- * @param array $native_strings An array of maps, where each map has a 'text'
- * field and a 'description' field.
- *
- * @return int Number of strings uploaded.
- */
- public function &fbml_uploadNativeStrings($native_strings) {
- return $this->call_method('facebook.fbml.uploadNativeStrings',
- array('native_strings' => json_encode($native_strings)));
- }
-
- /**
- * Associates a given "handle" with FBML markup so that the handle can be
- * used within the fb:ref FBML tag. A handle is unique within an application
- * and allows an application to publish identical FBML to many user profiles
- * and do subsequent updates without having to republish FBML on behalf of
- * each user.
- *
- * @param string $handle The handle to associate with the given FBML.
- * @param string $fbml The FBML to associate with the given handle.
- *
- * @return bool true on success
- */
- public function &fbml_setRefHandle($handle, $fbml) {
- return $this->call_method('facebook.fbml.setRefHandle',
- array('handle' => $handle, 'fbml' => $fbml));
- }
-
- /**
- * Register custom tags for the application. Custom tags can be used
- * to extend the set of tags available to applications in FBML
- * markup.
- *
- * Before you call this function,
- * make sure you read the full documentation at
- *
- * http://wiki.developers.facebook.com/index.php/Fbml.RegisterCustomTags
- *
- * IMPORTANT: This function overwrites the values of
- * existing tags if the names match. Use this function with care because
- * it may break the FBML of any application that is using the
- * existing version of the tags.
- *
- * @param mixed $tags an array of tag objects (the full description is on the
- * wiki page)
- *
- * @return int the number of tags that were registered
- */
- public function &fbml_registerCustomTags($tags) {
- $tags = json_encode($tags);
- return $this->call_method('facebook.fbml.registerCustomTags',
- array('tags' => $tags));
- }
-
- /**
- * Get the custom tags for an application. If $app_id
- * is not specified, the calling app's tags are returned.
- * If $app_id is different from the id of the calling app,
- * only the app's public tags are returned.
- * The return value is an array of the same type as
- * the $tags parameter of fbml_registerCustomTags().
- *
- * @param int $app_id the application's id (optional)
- *
- * @return mixed an array containing the custom tag objects
- */
- public function &fbml_getCustomTags($app_id = null) {
- return $this->call_method('facebook.fbml.getCustomTags',
- array('app_id' => $app_id));
- }
-
-
- /**
- * Delete custom tags the application has registered. If
- * $tag_names is null, all the application's custom tags will be
- * deleted.
- *
- * IMPORTANT: If your application has registered public tags
- * that other applications may be using, don't delete those tags!
- * Doing so can break the FBML ofapplications that are using them.
- *
- * @param array $tag_names the names of the tags to delete (optinal)
- * @return bool true on success
- */
- public function &fbml_deleteCustomTags($tag_names = null) {
- return $this->call_method('facebook.fbml.deleteCustomTags',
- array('tag_names' => json_encode($tag_names)));
- }
-
-
-
- /**
- * This method is deprecated for calls made on behalf of users. This method
- * works only for publishing stories on a Facebook Page that has installed
- * your application. To publish stories to a user's profile, use
- * feed.publishUserAction instead.
- *
- * For more details on this call, please visit the wiki page:
- *
- * http://wiki.developers.facebook.com/index.php/Feed.publishTemplatizedAction
- */
- public function &feed_publishTemplatizedAction($title_template,
- $title_data,
- $body_template,
- $body_data,
- $body_general,
- $image_1=null,
- $image_1_link=null,
- $image_2=null,
- $image_2_link=null,
- $image_3=null,
- $image_3_link=null,
- $image_4=null,
- $image_4_link=null,
- $target_ids='',
- $page_actor_id=null) {
- return $this->call_method('facebook.feed.publishTemplatizedAction',
- array('title_template' => $title_template,
- 'title_data' => $title_data,
- 'body_template' => $body_template,
- 'body_data' => $body_data,
- 'body_general' => $body_general,
- 'image_1' => $image_1,
- 'image_1_link' => $image_1_link,
- 'image_2' => $image_2,
- 'image_2_link' => $image_2_link,
- 'image_3' => $image_3,
- 'image_3_link' => $image_3_link,
- 'image_4' => $image_4,
- 'image_4_link' => $image_4_link,
- 'target_ids' => $target_ids,
- 'page_actor_id' => $page_actor_id));
- }
-
- /**
- * Registers a template bundle. Template bundles are somewhat involved, so
- * it's recommended you check out the wiki for more details:
- *
- * http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle
- *
- * @return string A template bundle id
- */
- public function &feed_registerTemplateBundle($one_line_story_templates,
- $short_story_templates = array(),
- $full_story_template = null,
- $action_links = array()) {
-
- $one_line_story_templates = json_encode($one_line_story_templates);
-
- if (!empty($short_story_templates)) {
- $short_story_templates = json_encode($short_story_templates);
- }
-
- if (isset($full_story_template)) {
- $full_story_template = json_encode($full_story_template);
- }
-
- if (isset($action_links)) {
- $action_links = json_encode($action_links);
- }
-
- return $this->call_method('facebook.feed.registerTemplateBundle',
- array('one_line_story_templates' => $one_line_story_templates,
- 'short_story_templates' => $short_story_templates,
- 'full_story_template' => $full_story_template,
- 'action_links' => $action_links));
- }
-
- /**
- * Retrieves the full list of active template bundles registered by the
- * requesting application.
- *
- * @return array An array of template bundles
- */
- public function &feed_getRegisteredTemplateBundles() {
- return $this->call_method('facebook.feed.getRegisteredTemplateBundles',
- array());
- }
-
- /**
- * Retrieves information about a specified template bundle previously
- * registered by the requesting application.
- *
- * @param string $template_bundle_id The template bundle id
- *
- * @return array Template bundle
- */
- public function &feed_getRegisteredTemplateBundleByID($template_bundle_id) {
- return $this->call_method('facebook.feed.getRegisteredTemplateBundleByID',
- array('template_bundle_id' => $template_bundle_id));
- }
-
- /**
- * Deactivates a previously registered template bundle.
- *
- * @param string $template_bundle_id The template bundle id
- *
- * @return bool true on success
- */
- public function &feed_deactivateTemplateBundleByID($template_bundle_id) {
- return $this->call_method('facebook.feed.deactivateTemplateBundleByID',
- array('template_bundle_id' => $template_bundle_id));
- }
-
- const STORY_SIZE_ONE_LINE = 1;
- const STORY_SIZE_SHORT = 2;
- const STORY_SIZE_FULL = 4;
-
- /**
- * Publishes a story on behalf of the user owning the session, using the
- * specified template bundle. This method requires an active session key in
- * order to be called.
- *
- * The parameters to this method ($templata_data in particular) are somewhat
- * involved. It's recommended you visit the wiki for details:
- *
- * http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
- *
- * @param int $template_bundle_id A template bundle id previously registered
- * @param array $template_data See wiki article for syntax
- * @param array $target_ids (Optional) An array of friend uids of the
- * user who shared in this action.
- * @param string $body_general (Optional) Additional markup that extends
- * the body of a short story.
- * @param int $story_size (Optional) A story size (see above)
- * @param string $user_message (Optional) A user message for a short
- * story.
- *
- * @return bool true on success
- */
- public function &feed_publishUserAction(
- $template_bundle_id, $template_data, $target_ids='', $body_general='',
- $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE,
- $user_message='') {
-
- if (is_array($template_data)) {
- $template_data = json_encode($template_data);
- } // allow client to either pass in JSON or an assoc that we JSON for them
-
- if (is_array($target_ids)) {
- $target_ids = json_encode($target_ids);
- $target_ids = trim($target_ids, "[]"); // we don't want square brackets
- }
-
- return $this->call_method('facebook.feed.publishUserAction',
- array('template_bundle_id' => $template_bundle_id,
- 'template_data' => $template_data,
- 'target_ids' => $target_ids,
- 'body_general' => $body_general,
- 'story_size' => $story_size,
- 'user_message' => $user_message));
- }
-
-
- /**
- * Publish a post to the user's stream.
- *
- * @param $message the user's message
- * @param $attachment the post's attachment (optional)
- * @param $action links the post's action links (optional)
- * @param $target_id the user on whose wall the post will be posted
- * (optional)
- * @param $uid the actor (defaults to session user)
- * @return string the post id
- */
- public function stream_publish(
- $message, $attachment = null, $action_links = null, $target_id = null,
- $uid = null) {
-
- return $this->call_method(
- 'facebook.stream.publish',
- array('message' => $message,
- 'attachment' => $attachment,
- 'action_links' => $action_links,
- 'target_id' => $target_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Remove a post from the user's stream.
- * Currently, you may only remove stories you application created.
- *
- * @param $post_id the post id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_remove($post_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.remove',
- array('post_id' => $post_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Add a comment to a stream post
- *
- * @param $post_id the post id
- * @param $comment the comment text
- * @param $uid the actor (defaults to session user)
- * @return string the id of the created comment
- */
- public function stream_addComment($post_id, $comment, $uid = null) {
- return $this->call_method(
- 'facebook.stream.addComment',
- array('post_id' => $post_id,
- 'comment' => $comment,
- 'uid' => $this->get_uid($uid)));
- }
-
-
- /**
- * Remove a comment from a stream post
- *
- * @param $comment_id the comment id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_removeComment($comment_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.removeComment',
- array('comment_id' => $comment_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Add a like to a stream post
- *
- * @param $post_id the post id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_addLike($post_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.addLike',
- array('post_id' => $post_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Remove a like from a stream post
- *
- * @param $post_id the post id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_removeLike($post_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.removeLike',
- array('post_id' => $post_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * For the current user, retrieves stories generated by the user's friends
- * while using this application. This can be used to easily create a
- * "News Feed" like experience.
- *
- * @return array An array of feed story objects.
- */
- public function &feed_getAppFriendStories() {
- return $this->call_method('facebook.feed.getAppFriendStories');
- }
-
- /**
- * Makes an FQL query. This is a generalized way of accessing all the data
- * in the API, as an alternative to most of the other method calls. More
- * info at http://wiki.developers.facebook.com/index.php/FQL
- *
- * @param string $query the query to evaluate
- *
- * @return array generalized array representing the results
- */
- public function &fql_query($query) {
- return $this->call_method('facebook.fql.query',
- array('query' => $query));
- }
-
- /**
- * Makes a set of FQL queries in parallel. This method takes a dictionary
- * of FQL queries where the keys are names for the queries. Results from
- * one query can be used within another query to fetch additional data. More
- * info about FQL queries at http://wiki.developers.facebook.com/index.php/FQL
- *
- * @param string $queries JSON-encoded dictionary of queries to evaluate
- *
- * @return array generalized array representing the results
- */
- public function &fql_multiquery($queries) {
- return $this->call_method('facebook.fql.multiquery',
- array('queries' => $queries));
- }
-
- /**
- * Returns whether or not pairs of users are friends.
- * Note that the Facebook friend relationship is symmetric.
- *
- * @param array/string $uids1 list of ids (id_1, id_2,...)
- * of some length X (csv is deprecated)
- * @param array/string $uids2 list of ids (id_A, id_B,...)
- * of SAME length X (csv is deprecated)
- *
- * @return array An array with uid1, uid2, and bool if friends, e.g.:
- * array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1),
- * 1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0)
- * ...)
- * @error
- * API_EC_PARAM_USER_ID_LIST
- */
- public function &friends_areFriends($uids1, $uids2) {
- return $this->call_method('facebook.friends.areFriends',
- array('uids1' => $uids1,
- 'uids2' => $uids2));
- }
-
- /**
- * Returns the friends of the current session user.
- *
- * @param int $flid (Optional) Only return friends on this friend list.
- * @param int $uid (Optional) Return friends for this user.
- *
- * @return array An array of friends
- */
- public function &friends_get($flid=null, $uid = null) {
- if (isset($this->friends_list)) {
- return $this->friends_list;
- }
- $params = array();
- if (!$uid && isset($this->canvas_user)) {
- $uid = $this->canvas_user;
- }
- if ($uid) {
- $params['uid'] = $uid;
- }
- if ($flid) {
- $params['flid'] = $flid;
- }
- return $this->call_method('facebook.friends.get', $params);
-
- }
-
- /**
- * Returns the mutual friends between the target uid and a source uid or
- * the current session user.
- *
- * @param int $target_uid Target uid for which mutual friends will be found.
- * @param int $source_uid (optional) Source uid for which mutual friends will
- * be found. If no source_uid is specified,
- * source_id will default to the session
- * user.
- * @return array An array of friend uids
- */
- public function &friends_getMutualFriends($target_uid, $source_uid = null) {
- return $this->call_method('facebook.friends.getMutualFriends',
- array("target_uid" => $target_uid,
- "source_uid" => $source_uid));
- }
-
- /**
- * Returns the set of friend lists for the current session user.
- *
- * @return array An array of friend list objects
- */
- public function &friends_getLists() {
- return $this->call_method('facebook.friends.getLists');
- }
-
- /**
- * Returns the friends of the session user, who are also users
- * of the calling application.
- *
- * @return array An array of friends also using the app
- */
- public function &friends_getAppUsers() {
- return $this->call_method('facebook.friends.getAppUsers');
- }
-
- /**
- * Returns groups according to the filters specified.
- *
- * @param int $uid (Optional) User associated with groups. A null
- * parameter will default to the session user.
- * @param array/string $gids (Optional) Array of group ids to query. A null
- * parameter will get all groups for the user.
- * (csv is deprecated)
- *
- * @return array An array of group objects
- */
- public function &groups_get($uid, $gids) {
- return $this->call_method('facebook.groups.get',
- array('uid' => $uid,
- 'gids' => $gids));
- }
-
- /**
- * Returns the membership list of a group.
- *
- * @param int $gid Group id
- *
- * @return array An array with four membership lists, with keys 'members',
- * 'admins', 'officers', and 'not_replied'
- */
- public function &groups_getMembers($gid) {
- return $this->call_method('facebook.groups.getMembers',
- array('gid' => $gid));
- }
-
- /**
- * Returns cookies according to the filters specified.
- *
- * @param int $uid User for which the cookies are needed.
- * @param string $name (Optional) A null parameter will get all cookies
- * for the user.
- *
- * @return array Cookies! Nom nom nom nom nom.
- */
- public function data_getCookies($uid, $name) {
- return $this->call_method('facebook.data.getCookies',
- array('uid' => $uid,
- 'name' => $name));
- }
-
- /**
- * Sets cookies according to the params specified.
- *
- * @param int $uid User for which the cookies are needed.
- * @param string $name Name of the cookie
- * @param string $value (Optional) if expires specified and is in the past
- * @param int $expires (Optional) Expiry time
- * @param string $path (Optional) Url path to associate with (default is /)
- *
- * @return bool true on success
- */
- public function data_setCookie($uid, $name, $value, $expires, $path) {
- return $this->call_method('facebook.data.setCookie',
- array('uid' => $uid,
- 'name' => $name,
- 'value' => $value,
- 'expires' => $expires,
- 'path' => $path));
- }
-
- /**
- * Retrieves links posted by the given user.
- *
- * @param int $uid The user whose links you wish to retrieve
- * @param int $limit The maximimum number of links to retrieve
- * @param array $link_ids (Optional) Array of specific link
- * IDs to retrieve by this user
- *
- * @return array An array of links.
- */
- public function &links_get($uid, $limit, $link_ids = null) {
- return $this->call_method('links.get',
- array('uid' => $uid,
- 'limit' => $limit,
- 'link_ids' => $link_ids));
- }
-
- /**
- * Posts a link on Facebook.
- *
- * @param string $url URL/link you wish to post
- * @param string $comment (Optional) A comment about this link
- * @param int $uid (Optional) User ID that is posting this link;
- * defaults to current session user
- *
- * @return bool
- */
- public function &links_post($url, $comment='', $uid = null) {
- return $this->call_method('links.post',
- array('uid' => $uid,
- 'url' => $url,
- 'comment' => $comment));
- }
-
- /**
- * Permissions API
- */
-
- /**
- * Checks API-access granted by self to the specified application.
- *
- * @param string $permissions_apikey Other application key
- *
- * @return array API methods/namespaces which are allowed access
- */
- public function permissions_checkGrantedApiAccess($permissions_apikey) {
- return $this->call_method('facebook.permissions.checkGrantedApiAccess',
- array('permissions_apikey' => $permissions_apikey));
- }
-
- /**
- * Checks API-access granted to self by the specified application.
- *
- * @param string $permissions_apikey Other application key
- *
- * @return array API methods/namespaces which are allowed access
- */
- public function permissions_checkAvailableApiAccess($permissions_apikey) {
- return $this->call_method('facebook.permissions.checkAvailableApiAccess',
- array('permissions_apikey' => $permissions_apikey));
- }
-
- /**
- * Grant API-access to the specified methods/namespaces to the specified
- * application.
- *
- * @param string $permissions_apikey Other application key
- * @param array(string) $method_arr (Optional) API methods/namespaces
- * allowed
- *
- * @return array API methods/namespaces which are allowed access
- */
- public function permissions_grantApiAccess($permissions_apikey, $method_arr) {
- return $this->call_method('facebook.permissions.grantApiAccess',
- array('permissions_apikey' => $permissions_apikey,
- 'method_arr' => $method_arr));
- }
-
- /**
- * Revoke API-access granted to the specified application.
- *
- * @param string $permissions_apikey Other application key
- *
- * @return bool true on success
- */
- public function permissions_revokeApiAccess($permissions_apikey) {
- return $this->call_method('facebook.permissions.revokeApiAccess',
- array('permissions_apikey' => $permissions_apikey));
- }
-
- /**
- * Payments Order API
- */
-
- /**
- * Set Payments properties for an app.
- *
- * @param properties a map from property names to values
- * @return true on success
- */
- public function payments_setProperties($properties) {
- return $this->call_method ('facebook.payments.setProperties',
- array('properties' => json_encode($properties)));
- }
-
- public function payments_getOrderDetails($order_id) {
- return json_decode($this->call_method(
- 'facebook.payments.getOrderDetails',
- array('order_id' => $order_id)), true);
- }
-
- public function payments_updateOrder($order_id, $status,
- $params) {
- return $this->call_method('facebook.payments.updateOrder',
- array('order_id' => $order_id,
- 'status' => $status,
- 'params' => json_encode($params)));
- }
-
- public function payments_getOrders($status, $start_time,
- $end_time, $test_mode=false) {
- return json_decode($this->call_method('facebook.payments.getOrders',
- array('status' => $status,
- 'start_time' => $start_time,
- 'end_time' => $end_time,
- 'test_mode' => $test_mode)), true);
- }
-
- /**
- * Creates a note with the specified title and content.
- *
- * @param string $title Title of the note.
- * @param string $content Content of the note.
- * @param int $uid (Optional) The user for whom you are creating a
- * note; defaults to current session user
- *
- * @return int The ID of the note that was just created.
- */
- public function &notes_create($title, $content, $uid = null) {
- return $this->call_method('notes.create',
- array('uid' => $uid,
- 'title' => $title,
- 'content' => $content));
- }
-
- /**
- * Deletes the specified note.
- *
- * @param int $note_id ID of the note you wish to delete
- * @param int $uid (Optional) Owner of the note you wish to delete;
- * defaults to current session user
- *
- * @return bool
- */
- public function &notes_delete($note_id, $uid = null) {
- return $this->call_method('notes.delete',
- array('uid' => $uid,
- 'note_id' => $note_id));
- }
-
- /**
- * Edits a note, replacing its title and contents with the title
- * and contents specified.
- *
- * @param int $note_id ID of the note you wish to edit
- * @param string $title Replacement title for the note
- * @param string $content Replacement content for the note
- * @param int $uid (Optional) Owner of the note you wish to edit;
- * defaults to current session user
- *
- * @return bool
- */
- public function &notes_edit($note_id, $title, $content, $uid = null) {
- return $this->call_method('notes.edit',
- array('uid' => $uid,
- 'note_id' => $note_id,
- 'title' => $title,
- 'content' => $content));
- }
-
- /**
- * Retrieves all notes by a user. If note_ids are specified,
- * retrieves only those specific notes by that user.
- *
- * @param int $uid User whose notes you wish to retrieve
- * @param array $note_ids (Optional) List of specific note
- * IDs by this user to retrieve
- *
- * @return array A list of all of the given user's notes, or an empty list
- * if the viewer lacks permissions or if there are no visible
- * notes.
- */
- public function &notes_get($uid, $note_ids = null) {
- return $this->call_method('notes.get',
- array('uid' => $uid,
- 'note_ids' => $note_ids));
- }
-
-
- /**
- * Returns the outstanding notifications for the session user.
- *
- * @return array An assoc array of notification count objects for
- * 'messages', 'pokes' and 'shares', a uid list of
- * 'friend_requests', a gid list of 'group_invites',
- * and an eid list of 'event_invites'
- */
- public function &notifications_get() {
- return $this->call_method('facebook.notifications.get');
- }
-
- /**
- * Sends a notification to the specified users.
- *
- * @return A comma separated list of successful recipients
- * @error
- * API_EC_PARAM_USER_ID_LIST
- */
- public function &notifications_send($to_ids, $notification, $type) {
- return $this->call_method('facebook.notifications.send',
- array('to_ids' => $to_ids,
- 'notification' => $notification,
- 'type' => $type));
- }
-
- /**
- * Sends an email to the specified user of the application.
- *
- * @param array/string $recipients array of ids of the recipients (csv is deprecated)
- * @param string $subject subject of the email
- * @param string $text (plain text) body of the email
- * @param string $fbml fbml markup for an html version of the email
- *
- * @return string A comma separated list of successful recipients
- * @error
- * API_EC_PARAM_USER_ID_LIST
- */
- public function &notifications_sendEmail($recipients,
- $subject,
- $text,
- $fbml) {
- return $this->call_method('facebook.notifications.sendEmail',
- array('recipients' => $recipients,
- 'subject' => $subject,
- 'text' => $text,
- 'fbml' => $fbml));
- }
-
- /**
- * Returns the requested info fields for the requested set of pages.
- *
- * @param array/string $page_ids an array of page ids (csv is deprecated)
- * @param array/string $fields an array of strings describing the
- * info fields desired (csv is deprecated)
- * @param int $uid (Optional) limit results to pages of which this
- * user is a fan.
- * @param string type limits results to a particular type of page.
- *
- * @return array An array of pages
- */
- public function &pages_getInfo($page_ids, $fields, $uid, $type) {
- return $this->call_method('facebook.pages.getInfo',
- array('page_ids' => $page_ids,
- 'fields' => $fields,
- 'uid' => $uid,
- 'type' => $type));
- }
-
- /**
- * Returns true if the given user is an admin for the passed page.
- *
- * @param int $page_id target page id
- * @param int $uid (Optional) user id (defaults to the logged-in user)
- *
- * @return bool true on success
- */
- public function &pages_isAdmin($page_id, $uid = null) {
- return $this->call_method('facebook.pages.isAdmin',
- array('page_id' => $page_id,
- 'uid' => $uid));
- }
-
- /**
- * Returns whether or not the given page has added the application.
- *
- * @param int $page_id target page id
- *
- * @return bool true on success
- */
- public function &pages_isAppAdded($page_id) {
- return $this->call_method('facebook.pages.isAppAdded',
- array('page_id' => $page_id));
- }
-
- /**
- * Returns true if logged in user is a fan for the passed page.
- *
- * @param int $page_id target page id
- * @param int $uid user to compare. If empty, the logged in user.
- *
- * @return bool true on success
- */
- public function &pages_isFan($page_id, $uid = null) {
- return $this->call_method('facebook.pages.isFan',
- array('page_id' => $page_id,
- 'uid' => $uid));
- }
-
- /**
- * Adds a tag with the given information to a photo. See the wiki for details:
- *
- * http://wiki.developers.facebook.com/index.php/Photos.addTag
- *
- * @param int $pid The ID of the photo to be tagged
- * @param int $tag_uid The ID of the user being tagged. You must specify
- * either the $tag_uid or the $tag_text parameter
- * (unless $tags is specified).
- * @param string $tag_text Some text identifying the person being tagged.
- * You must specify either the $tag_uid or $tag_text
- * parameter (unless $tags is specified).
- * @param float $x The horizontal position of the tag, as a
- * percentage from 0 to 100, from the left of the
- * photo.
- * @param float $y The vertical position of the tag, as a percentage
- * from 0 to 100, from the top of the photo.
- * @param array $tags (Optional) An array of maps, where each map
- * can contain the tag_uid, tag_text, x, and y
- * parameters defined above. If specified, the
- * individual arguments are ignored.
- * @param int $owner_uid (Optional) The user ID of the user whose photo
- * you are tagging. If this parameter is not
- * specified, then it defaults to the session user.
- *
- * @return bool true on success
- */
- public function &photos_addTag($pid,
- $tag_uid,
- $tag_text,
- $x,
- $y,
- $tags,
- $owner_uid=0) {
- return $this->call_method('facebook.photos.addTag',
- array('pid' => $pid,
- 'tag_uid' => $tag_uid,
- 'tag_text' => $tag_text,
- 'x' => $x,
- 'y' => $y,
- 'tags' => (is_array($tags)) ? json_encode($tags) : null,
- 'owner_uid' => $this->get_uid($owner_uid)));
- }
-
- /**
- * Creates and returns a new album owned by the specified user or the current
- * session user.
- *
- * @param string $name The name of the album.
- * @param string $description (Optional) A description of the album.
- * @param string $location (Optional) A description of the location.
- * @param string $visible (Optional) A privacy setting for the album.
- * One of 'friends', 'friends-of-friends',
- * 'networks', or 'everyone'. Default 'everyone'.
- * @param int $uid (Optional) User id for creating the album; if
- * not specified, the session user is used.
- *
- * @return array An album object
- */
- public function &photos_createAlbum($name,
- $description='',
- $location='',
- $visible='',
- $uid=0) {
- return $this->call_method('facebook.photos.createAlbum',
- array('name' => $name,
- 'description' => $description,
- 'location' => $location,
- 'visible' => $visible,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Returns photos according to the filters specified.
- *
- * @param int $subj_id (Optional) Filter by uid of user tagged in the photos.
- * @param int $aid (Optional) Filter by an album, as returned by
- * photos_getAlbums.
- * @param array/string $pids (Optional) Restrict to an array of pids
- * (csv is deprecated)
- *
- * Note that at least one of these parameters needs to be specified, or an
- * error is returned.
- *
- * @return array An array of photo objects.
- */
- public function &photos_get($subj_id, $aid, $pids) {
- return $this->call_method('facebook.photos.get',
- array('subj_id' => $subj_id, 'aid' => $aid, 'pids' => $pids));
- }
-
- /**
- * Returns the albums created by the given user.
- *
- * @param int $uid (Optional) The uid of the user whose albums you want.
- * A null will return the albums of the session user.
- * @param string $aids (Optional) An array of aids to restrict
- * the query. (csv is deprecated)
- *
- * Note that at least one of the (uid, aids) parameters must be specified.
- *
- * @returns an array of album objects.
- */
- public function &photos_getAlbums($uid, $aids) {
- return $this->call_method('facebook.photos.getAlbums',
- array('uid' => $uid,
- 'aids' => $aids));
- }
-
- /**
- * Returns the tags on all photos specified.
- *
- * @param string $pids A list of pids to query
- *
- * @return array An array of photo tag objects, which include pid,
- * subject uid, and two floating-point numbers (xcoord, ycoord)
- * for tag pixel location.
- */
- public function &photos_getTags($pids) {
- return $this->call_method('facebook.photos.getTags',
- array('pids' => $pids));
- }
-
- /**
- * Uploads a photo.
- *
- * @param string $file The location of the photo on the local filesystem.
- * @param int $aid (Optional) The album into which to upload the
- * photo.
- * @param string $caption (Optional) A caption for the photo.
- * @param int uid (Optional) The user ID of the user whose photo you
- * are uploading
- *
- * @return array An array of user objects
- */
- public function photos_upload($file, $aid=null, $caption=null, $uid=null) {
- return $this->call_upload_method('facebook.photos.upload',
- array('aid' => $aid,
- 'caption' => $caption,
- 'uid' => $uid),
- $file);
- }
-
-
- /**
- * Uploads a video.
- *
- * @param string $file The location of the video on the local filesystem.
- * @param string $title (Optional) A title for the video. Titles over 65 characters in length will be truncated.
- * @param string $description (Optional) A description for the video.
- *
- * @return array An array with the video's ID, title, description, and a link to view it on Facebook.
- */
- public function video_upload($file, $title=null, $description=null) {
- return $this->call_upload_method('facebook.video.upload',
- array('title' => $title,
- 'description' => $description),
- $file,
- Facebook::get_facebook_url('api-video') . '/restserver.php');
- }
-
- /**
- * Returns an array with the video limitations imposed on the current session's
- * associated user. Maximum length is measured in seconds; maximum size is
- * measured in bytes.
- *
- * @return array Array with "length" and "size" keys
- */
- public function &video_getUploadLimits() {
- return $this->call_method('facebook.video.getUploadLimits');
- }
-
- /**
- * Returns the requested info fields for the requested set of users.
- *
- * @param array/string $uids An array of user ids (csv is deprecated)
- * @param array/string $fields An array of info field names desired (csv is deprecated)
- *
- * @return array An array of user objects
- */
- public function &users_getInfo($uids, $fields) {
- return $this->call_method('facebook.users.getInfo',
- array('uids' => $uids,
- 'fields' => $fields));
- }
-
- /**
- * Returns the requested info fields for the requested set of users. A
- * session key must not be specified. Only data about users that have
- * authorized your application will be returned.
- *
- * Check the wiki for fields that can be queried through this API call.
- * Data returned from here should not be used for rendering to application
- * users, use users.getInfo instead, so that proper privacy rules will be
- * applied.
- *
- * @param array/string $uids An array of user ids (csv is deprecated)
- * @param array/string $fields An array of info field names desired (csv is deprecated)
- *
- * @return array An array of user objects
- */
- public function &users_getStandardInfo($uids, $fields) {
- return $this->call_method('facebook.users.getStandardInfo',
- array('uids' => $uids,
- 'fields' => $fields));
- }
-
- /**
- * Returns the user corresponding to the current session object.
- *
- * @return integer User id
- */
- public function &users_getLoggedInUser() {
- return $this->call_method('facebook.users.getLoggedInUser');
- }
-
- /**
- * Returns 1 if the user has the specified permission, 0 otherwise.
- * http://wiki.developers.facebook.com/index.php/Users.hasAppPermission
- *
- * @return integer 1 or 0
- */
- public function &users_hasAppPermission($ext_perm, $uid=null) {
- return $this->call_method('facebook.users.hasAppPermission',
- array('ext_perm' => $ext_perm, 'uid' => $uid));
- }
-
- /**
- * Returns whether or not the user corresponding to the current
- * session object has the give the app basic authorization.
- *
- * @return boolean true if the user has authorized the app
- */
- public function &users_isAppUser($uid=null) {
- if ($uid === null && isset($this->is_user)) {
- return $this->is_user;
- }
-
- return $this->call_method('facebook.users.isAppUser', array('uid' => $uid));
- }
-
- /**
- * Returns whether or not the user corresponding to the current
- * session object is verified by Facebook. See the documentation
- * for Users.isVerified for details.
- *
- * @return boolean true if the user is verified
- */
- public function &users_isVerified() {
- return $this->call_method('facebook.users.isVerified');
- }
-
- /**
- * Sets the users' current status message. Message does NOT contain the
- * word "is" , so make sure to include a verb.
- *
- * Example: setStatus("is loving the API!")
- * will produce the status "Luke is loving the API!"
- *
- * @param string $status text-only message to set
- * @param int $uid user to set for (defaults to the
- * logged-in user)
- * @param bool $clear whether or not to clear the status,
- * instead of setting it
- * @param bool $status_includes_verb if true, the word "is" will *not* be
- * prepended to the status message
- *
- * @return boolean
- */
- public function &users_setStatus($status,
- $uid = null,
- $clear = false,
- $status_includes_verb = true) {
- $args = array(
- 'status' => $status,
- 'uid' => $uid,
- 'clear' => $clear,
- 'status_includes_verb' => $status_includes_verb,
- );
- return $this->call_method('facebook.users.setStatus', $args);
- }
-
- /**
- * Gets the comments for a particular xid. This is essentially a wrapper
- * around the comment FQL table.
- *
- * @param string $xid external id associated with the comments
- *
- * @return array of comment objects
- */
- public function &comments_get($xid) {
- $args = array('xid' => $xid);
- return $this->call_method('facebook.comments.get', $args);
- }
-
- /**
- * Add a comment to a particular xid on behalf of a user. If called
- * without an app_secret (with session secret), this will only work
- * for the session user.
- *
- * @param string $xid external id associated with the comments
- * @param string $text text of the comment
- * @param int $uid user adding the comment (def: session user)
- * @param string $title optional title for the stream story
- * @param string $url optional url for the stream story
- * @param bool $publish_to_stream publish a feed story about this comment?
- * a link will be generated to title/url in the story
- *
- * @return string comment_id associated with the comment
- */
- public function &comments_add($xid, $text, $uid=0, $title='', $url='',
- $publish_to_stream=false) {
- $args = array(
- 'xid' => $xid,
- 'uid' => $this->get_uid($uid),
- 'text' => $text,
- 'title' => $title,
- 'url' => $url,
- 'publish_to_stream' => $publish_to_stream);
-
- return $this->call_method('facebook.comments.add', $args);
- }
-
- /**
- * Remove a particular comment.
- *
- * @param string $xid the external id associated with the comments
- * @param string $comment_id id of the comment to remove (returned by
- * comments.add and comments.get)
- *
- * @return boolean
- */
- public function &comments_remove($xid, $comment_id) {
- $args = array(
- 'xid' => $xid,
- 'comment_id' => $comment_id);
- return $this->call_method('facebook.comments.remove', $args);
- }
-
- /**
- * Gets the stream on behalf of a user using a set of users. This
- * call will return the latest $limit queries between $start_time
- * and $end_time.
- *
- * @param int $viewer_id user making the call (def: session)
- * @param array $source_ids users/pages to look at (def: all connections)
- * @param int $start_time start time to look for stories (def: 1 day ago)
- * @param int $end_time end time to look for stories (def: now)
- * @param int $limit number of stories to attempt to fetch (def: 30)
- * @param string $filter_key key returned by stream.getFilters to fetch
- * @param array $metadata metadata to include with the return, allows
- * requested metadata to be returned, such as
- * profiles, albums, photo_tags
- *
- * @return array(
- * 'posts' => array of posts,
- * // if requested, the following data may be returned
- * 'profiles' => array of profile metadata of users/pages in posts
- * 'albums' => array of album metadata in posts
- * 'photo_tags' => array of photo_tags for photos in posts
- * )
- */
- public function &stream_get($viewer_id = null,
- $source_ids = null,
- $start_time = 0,
- $end_time = 0,
- $limit = 30,
- $filter_key = '') {
- $args = array(
- 'viewer_id' => $viewer_id,
- 'source_ids' => $source_ids,
- 'start_time' => $start_time,
- 'end_time' => $end_time,
- 'limit' => $limit,
- 'filter_key' => $filter_key);
- return $this->call_method('facebook.stream.get', $args);
- }
-
- /**
- * Gets the filters (with relevant filter keys for stream.get) for a
- * particular user. These filters are typical things like news feed,
- * friend lists, networks. They can be used to filter the stream
- * without complex queries to determine which ids belong in which groups.
- *
- * @param int $uid user to get filters for
- *
- * @return array of stream filter objects
- */
- public function &stream_getFilters($uid = null) {
- $args = array('uid' => $uid);
- return $this->call_method('facebook.stream.getFilters', $args);
- }
-
- /**
- * Gets the full comments given a post_id from stream.get or the
- * stream FQL table. Initially, only a set of preview comments are
- * returned because some posts can have many comments.
- *
- * @param string $post_id id of the post to get comments for
- *
- * @return array of comment objects
- */
- public function &stream_getComments($post_id) {
- $args = array('post_id' => $post_id);
- return $this->call_method('facebook.stream.getComments', $args);
- }
-
- /**
- * Sets the FBML for the profile of the user attached to this session.
- *
- * @param string $markup The FBML that describes the profile
- * presence of this app for the user
- * @param int $uid The user
- * @param string $profile Profile FBML
- * @param string $profile_action Profile action FBML (deprecated)
- * @param string $mobile_profile Mobile profile FBML
- * @param string $profile_main Main Tab profile FBML
- *
- * @return array A list of strings describing any compile errors for the
- * submitted FBML
- */
- function profile_setFBML($markup,
- $uid=null,
- $profile='',
- $profile_action='',
- $mobile_profile='',
- $profile_main='') {
- return $this->call_method('facebook.profile.setFBML',
- array('markup' => $markup,
- 'uid' => $uid,
- 'profile' => $profile,
- 'profile_action' => $profile_action,
- 'mobile_profile' => $mobile_profile,
- 'profile_main' => $profile_main));
- }
-
- /**
- * Gets the FBML for the profile box that is currently set for a user's
- * profile (your application set the FBML previously by calling the
- * profile.setFBML method).
- *
- * @param int $uid (Optional) User id to lookup; defaults to session.
- * @param int $type (Optional) 1 for original style, 2 for profile_main boxes
- *
- * @return string The FBML
- */
- public function &profile_getFBML($uid=null, $type=null) {
- return $this->call_method('facebook.profile.getFBML',
- array('uid' => $uid,
- 'type' => $type));
- }
-
- /**
- * Returns the specified user's application info section for the calling
- * application. These info sections have either been set via a previous
- * profile.setInfo call or by the user editing them directly.
- *
- * @param int $uid (Optional) User id to lookup; defaults to session.
- *
- * @return array Info fields for the current user. See wiki for structure:
- *
- * http://wiki.developers.facebook.com/index.php/Profile.getInfo
- *
- */
- public function &profile_getInfo($uid=null) {
- return $this->call_method('facebook.profile.getInfo',
- array('uid' => $uid));
- }
-
- /**
- * Returns the options associated with the specified info field for an
- * application info section.
- *
- * @param string $field The title of the field
- *
- * @return array An array of info options.
- */
- public function &profile_getInfoOptions($field) {
- return $this->call_method('facebook.profile.getInfoOptions',
- array('field' => $field));
- }
-
- /**
- * Configures an application info section that the specified user can install
- * on the Info tab of her profile. For details on the structure of an info
- * field, please see:
- *
- * http://wiki.developers.facebook.com/index.php/Profile.setInfo
- *
- * @param string $title Title / header of the info section
- * @param int $type 1 for text-only, 5 for thumbnail views
- * @param array $info_fields An array of info fields. See wiki for details.
- * @param int $uid (Optional)
- *
- * @return bool true on success
- */
- public function &profile_setInfo($title, $type, $info_fields, $uid=null) {
- return $this->call_method('facebook.profile.setInfo',
- array('uid' => $uid,
- 'type' => $type,
- 'title' => $title,
- 'info_fields' => json_encode($info_fields)));
- }
-
- /**
- * Specifies the objects for a field for an application info section. These
- * options populate the typeahead for a thumbnail.
- *
- * @param string $field The title of the field
- * @param array $options An array of items for a thumbnail, including
- * 'label', 'link', and optionally 'image',
- * 'description' and 'sublabel'
- *
- * @return bool true on success
- */
- public function profile_setInfoOptions($field, $options) {
- return $this->call_method('facebook.profile.setInfoOptions',
- array('field' => $field,
- 'options' => json_encode($options)));
- }
-
- /**
- * Get all the marketplace categories.
- *
- * @return array A list of category names
- */
- function marketplace_getCategories() {
- return $this->call_method('facebook.marketplace.getCategories',
- array());
- }
-
- /**
- * Get all the marketplace subcategories for a particular category.
- *
- * @param category The category for which we are pulling subcategories
- *
- * @return array A list of subcategory names
- */
- function marketplace_getSubCategories($category) {
- return $this->call_method('facebook.marketplace.getSubCategories',
- array('category' => $category));
- }
-
- /**
- * Get listings by either listing_id or user.
- *
- * @param listing_ids An array of listing_ids (optional)
- * @param uids An array of user ids (optional)
- *
- * @return array The data for matched listings
- */
- function marketplace_getListings($listing_ids, $uids) {
- return $this->call_method('facebook.marketplace.getListings',
- array('listing_ids' => $listing_ids, 'uids' => $uids));
- }
-
- /**
- * Search for Marketplace listings. All arguments are optional, though at
- * least one must be filled out to retrieve results.
- *
- * @param category The category in which to search (optional)
- * @param subcategory The subcategory in which to search (optional)
- * @param query A query string (optional)
- *
- * @return array The data for matched listings
- */
- function marketplace_search($category, $subcategory, $query) {
- return $this->call_method('facebook.marketplace.search',
- array('category' => $category,
- 'subcategory' => $subcategory,
- 'query' => $query));
- }
-
- /**
- * Remove a listing from Marketplace.
- *
- * @param listing_id The id of the listing to be removed
- * @param status 'SUCCESS', 'NOT_SUCCESS', or 'DEFAULT'
- *
- * @return bool True on success
- */
- function marketplace_removeListing($listing_id,
- $status='DEFAULT',
- $uid=null) {
- return $this->call_method('facebook.marketplace.removeListing',
- array('listing_id' => $listing_id,
- 'status' => $status,
- 'uid' => $uid));
- }
-
- /**
- * Create/modify a Marketplace listing for the loggedinuser.
- *
- * @param int listing_id The id of a listing to be modified, 0
- * for a new listing.
- * @param show_on_profile bool Should we show this listing on the
- * user's profile
- * @param listing_attrs array An array of the listing data
- *
- * @return int The listing_id (unchanged if modifying an existing listing).
- */
- function marketplace_createListing($listing_id,
- $show_on_profile,
- $attrs,
- $uid=null) {
- return $this->call_method('facebook.marketplace.createListing',
- array('listing_id' => $listing_id,
- 'show_on_profile' => $show_on_profile,
- 'listing_attrs' => json_encode($attrs),
- 'uid' => $uid));
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // Data Store API
-
- /**
- * Set a user preference.
- *
- * @param pref_id preference identifier (0-200)
- * @param value preferece's value
- * @param uid the user id (defaults to current session user)
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_setUserPreference($pref_id, $value, $uid = null) {
- return $this->call_method('facebook.data.setUserPreference',
- array('pref_id' => $pref_id,
- 'value' => $value,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Set a user's all preferences for this application.
- *
- * @param values preferece values in an associative arrays
- * @param replace whether to replace all existing preferences or
- * merge into them.
- * @param uid the user id (defaults to current session user)
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_setUserPreferences($values,
- $replace = false,
- $uid = null) {
- return $this->call_method('facebook.data.setUserPreferences',
- array('values' => json_encode($values),
- 'replace' => $replace,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Get a user preference.
- *
- * @param pref_id preference identifier (0-200)
- * @param uid the user id (defaults to current session user)
- * @return preference's value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_getUserPreference($pref_id, $uid = null) {
- return $this->call_method('facebook.data.getUserPreference',
- array('pref_id' => $pref_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Get a user preference.
- *
- * @param uid the user id (defaults to current session user)
- * @return preference values
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_getUserPreferences($uid = null) {
- return $this->call_method('facebook.data.getUserPreferences',
- array('uid' => $this->get_uid($uid)));
- }
-
- /**
- * Create a new object type.
- *
- * @param name object type's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_createObjectType($name) {
- return $this->call_method('facebook.data.createObjectType',
- array('name' => $name));
- }
-
- /**
- * Delete an object type.
- *
- * @param obj_type object type's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_dropObjectType($obj_type) {
- return $this->call_method('facebook.data.dropObjectType',
- array('obj_type' => $obj_type));
- }
-
- /**
- * Rename an object type.
- *
- * @param obj_type object type's name
- * @param new_name new object type's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_renameObjectType($obj_type, $new_name) {
- return $this->call_method('facebook.data.renameObjectType',
- array('obj_type' => $obj_type,
- 'new_name' => $new_name));
- }
-
- /**
- * Add a new property to an object type.
- *
- * @param obj_type object type's name
- * @param prop_name name of the property to add
- * @param prop_type 1: integer; 2: string; 3: text blob
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_defineObjectProperty($obj_type,
- $prop_name,
- $prop_type) {
- return $this->call_method('facebook.data.defineObjectProperty',
- array('obj_type' => $obj_type,
- 'prop_name' => $prop_name,
- 'prop_type' => $prop_type));
- }
-
- /**
- * Remove a previously defined property from an object type.
- *
- * @param obj_type object type's name
- * @param prop_name name of the property to remove
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_undefineObjectProperty($obj_type, $prop_name) {
- return $this->call_method('facebook.data.undefineObjectProperty',
- array('obj_type' => $obj_type,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Rename a previously defined property of an object type.
- *
- * @param obj_type object type's name
- * @param prop_name name of the property to rename
- * @param new_name new name to use
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_renameObjectProperty($obj_type, $prop_name,
- $new_name) {
- return $this->call_method('facebook.data.renameObjectProperty',
- array('obj_type' => $obj_type,
- 'prop_name' => $prop_name,
- 'new_name' => $new_name));
- }
-
- /**
- * Retrieve a list of all object types that have defined for the application.
- *
- * @return a list of object type names
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjectTypes() {
- return $this->call_method('facebook.data.getObjectTypes');
- }
-
- /**
- * Get definitions of all properties of an object type.
- *
- * @param obj_type object type's name
- * @return pairs of property name and property types
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjectType($obj_type) {
- return $this->call_method('facebook.data.getObjectType',
- array('obj_type' => $obj_type));
- }
-
- /**
- * Create a new object.
- *
- * @param obj_type object type's name
- * @param properties (optional) properties to set initially
- * @return newly created object's id
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_createObject($obj_type, $properties = null) {
- return $this->call_method('facebook.data.createObject',
- array('obj_type' => $obj_type,
- 'properties' => json_encode($properties)));
- }
-
- /**
- * Update an existing object.
- *
- * @param obj_id object's id
- * @param properties new properties
- * @param replace true for replacing existing properties;
- * false for merging
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_updateObject($obj_id, $properties, $replace = false) {
- return $this->call_method('facebook.data.updateObject',
- array('obj_id' => $obj_id,
- 'properties' => json_encode($properties),
- 'replace' => $replace));
- }
-
- /**
- * Delete an existing object.
- *
- * @param obj_id object's id
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_deleteObject($obj_id) {
- return $this->call_method('facebook.data.deleteObject',
- array('obj_id' => $obj_id));
- }
-
- /**
- * Delete a list of objects.
- *
- * @param obj_ids objects to delete
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_deleteObjects($obj_ids) {
- return $this->call_method('facebook.data.deleteObjects',
- array('obj_ids' => json_encode($obj_ids)));
- }
-
- /**
- * Get a single property value of an object.
- *
- * @param obj_id object's id
- * @param prop_name individual property's name
- * @return individual property's value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjectProperty($obj_id, $prop_name) {
- return $this->call_method('facebook.data.getObjectProperty',
- array('obj_id' => $obj_id,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Get properties of an object.
- *
- * @param obj_id object's id
- * @param prop_names (optional) properties to return; null for all.
- * @return specified properties of an object
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObject($obj_id, $prop_names = null) {
- return $this->call_method('facebook.data.getObject',
- array('obj_id' => $obj_id,
- 'prop_names' => json_encode($prop_names)));
- }
-
- /**
- * Get properties of a list of objects.
- *
- * @param obj_ids object ids
- * @param prop_names (optional) properties to return; null for all.
- * @return specified properties of an object
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjects($obj_ids, $prop_names = null) {
- return $this->call_method('facebook.data.getObjects',
- array('obj_ids' => json_encode($obj_ids),
- 'prop_names' => json_encode($prop_names)));
- }
-
- /**
- * Set a single property value of an object.
- *
- * @param obj_id object's id
- * @param prop_name individual property's name
- * @param prop_value new value to set
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setObjectProperty($obj_id, $prop_name,
- $prop_value) {
- return $this->call_method('facebook.data.setObjectProperty',
- array('obj_id' => $obj_id,
- 'prop_name' => $prop_name,
- 'prop_value' => $prop_value));
- }
-
- /**
- * Read hash value by key.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @param prop_name (optional) individual property's name
- * @return hash value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getHashValue($obj_type, $key, $prop_name = null) {
- return $this->call_method('facebook.data.getHashValue',
- array('obj_type' => $obj_type,
- 'key' => $key,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Write hash value by key.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @param value hash value
- * @param prop_name (optional) individual property's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setHashValue($obj_type,
- $key,
- $value,
- $prop_name = null) {
- return $this->call_method('facebook.data.setHashValue',
- array('obj_type' => $obj_type,
- 'key' => $key,
- 'value' => $value,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Increase a hash value by specified increment atomically.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @param prop_name individual property's name
- * @param increment (optional) default is 1
- * @return incremented hash value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_incHashValue($obj_type,
- $key,
- $prop_name,
- $increment = 1) {
- return $this->call_method('facebook.data.incHashValue',
- array('obj_type' => $obj_type,
- 'key' => $key,
- 'prop_name' => $prop_name,
- 'increment' => $increment));
- }
-
- /**
- * Remove a hash key and its values.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeHashKey($obj_type, $key) {
- return $this->call_method('facebook.data.removeHashKey',
- array('obj_type' => $obj_type,
- 'key' => $key));
- }
-
- /**
- * Remove hash keys and their values.
- *
- * @param obj_type object type's name
- * @param keys hash keys
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeHashKeys($obj_type, $keys) {
- return $this->call_method('facebook.data.removeHashKeys',
- array('obj_type' => $obj_type,
- 'keys' => json_encode($keys)));
- }
-
- /**
- * Define an object association.
- *
- * @param name name of this association
- * @param assoc_type 1: one-way 2: two-way symmetric 3: two-way asymmetric
- * @param assoc_info1 needed info about first object type
- * @param assoc_info2 needed info about second object type
- * @param inverse (optional) name of reverse association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_defineAssociation($name, $assoc_type, $assoc_info1,
- $assoc_info2, $inverse = null) {
- return $this->call_method('facebook.data.defineAssociation',
- array('name' => $name,
- 'assoc_type' => $assoc_type,
- 'assoc_info1' => json_encode($assoc_info1),
- 'assoc_info2' => json_encode($assoc_info2),
- 'inverse' => $inverse));
- }
-
- /**
- * Undefine an object association.
- *
- * @param name name of this association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_undefineAssociation($name) {
- return $this->call_method('facebook.data.undefineAssociation',
- array('name' => $name));
- }
-
- /**
- * Rename an object association or aliases.
- *
- * @param name name of this association
- * @param new_name (optional) new name of this association
- * @param new_alias1 (optional) new alias for object type 1
- * @param new_alias2 (optional) new alias for object type 2
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_renameAssociation($name, $new_name, $new_alias1 = null,
- $new_alias2 = null) {
- return $this->call_method('facebook.data.renameAssociation',
- array('name' => $name,
- 'new_name' => $new_name,
- 'new_alias1' => $new_alias1,
- 'new_alias2' => $new_alias2));
- }
-
- /**
- * Get definition of an object association.
- *
- * @param name name of this association
- * @return specified association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociationDefinition($name) {
- return $this->call_method('facebook.data.getAssociationDefinition',
- array('name' => $name));
- }
-
- /**
- * Get definition of all associations.
- *
- * @return all defined associations
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociationDefinitions() {
- return $this->call_method('facebook.data.getAssociationDefinitions',
- array());
- }
-
- /**
- * Create or modify an association between two objects.
- *
- * @param name name of association
- * @param obj_id1 id of first object
- * @param obj_id2 id of second object
- * @param data (optional) extra string data to store
- * @param assoc_time (optional) extra time data; default to creation time
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setAssociation($name, $obj_id1, $obj_id2, $data = null,
- $assoc_time = null) {
- return $this->call_method('facebook.data.setAssociation',
- array('name' => $name,
- 'obj_id1' => $obj_id1,
- 'obj_id2' => $obj_id2,
- 'data' => $data,
- 'assoc_time' => $assoc_time));
- }
-
- /**
- * Create or modify associations between objects.
- *
- * @param assocs associations to set
- * @param name (optional) name of association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setAssociations($assocs, $name = null) {
- return $this->call_method('facebook.data.setAssociations',
- array('assocs' => json_encode($assocs),
- 'name' => $name));
- }
-
- /**
- * Remove an association between two objects.
- *
- * @param name name of association
- * @param obj_id1 id of first object
- * @param obj_id2 id of second object
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeAssociation($name, $obj_id1, $obj_id2) {
- return $this->call_method('facebook.data.removeAssociation',
- array('name' => $name,
- 'obj_id1' => $obj_id1,
- 'obj_id2' => $obj_id2));
- }
-
- /**
- * Remove associations between objects by specifying pairs of object ids.
- *
- * @param assocs associations to remove
- * @param name (optional) name of association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeAssociations($assocs, $name = null) {
- return $this->call_method('facebook.data.removeAssociations',
- array('assocs' => json_encode($assocs),
- 'name' => $name));
- }
-
- /**
- * Remove associations between objects by specifying one object id.
- *
- * @param name name of association
- * @param obj_id who's association to remove
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeAssociatedObjects($name, $obj_id) {
- return $this->call_method('facebook.data.removeAssociatedObjects',
- array('name' => $name,
- 'obj_id' => $obj_id));
- }
-
- /**
- * Retrieve a list of associated objects.
- *
- * @param name name of association
- * @param obj_id who's association to retrieve
- * @param no_data only return object ids
- * @return associated objects
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociatedObjects($name, $obj_id, $no_data = true) {
- return $this->call_method('facebook.data.getAssociatedObjects',
- array('name' => $name,
- 'obj_id' => $obj_id,
- 'no_data' => $no_data));
- }
-
- /**
- * Count associated objects.
- *
- * @param name name of association
- * @param obj_id who's association to retrieve
- * @return associated object's count
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociatedObjectCount($name, $obj_id) {
- return $this->call_method('facebook.data.getAssociatedObjectCount',
- array('name' => $name,
- 'obj_id' => $obj_id));
- }
-
- /**
- * Get a list of associated object counts.
- *
- * @param name name of association
- * @param obj_ids whose association to retrieve
- * @return associated object counts
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociatedObjectCounts($name, $obj_ids) {
- return $this->call_method('facebook.data.getAssociatedObjectCounts',
- array('name' => $name,
- 'obj_ids' => json_encode($obj_ids)));
- }
-
- /**
- * Find all associations between two objects.
- *
- * @param obj_id1 id of first object
- * @param obj_id2 id of second object
- * @param no_data only return association names without data
- * @return all associations between objects
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociations($obj_id1, $obj_id2, $no_data = true) {
- return $this->call_method('facebook.data.getAssociations',
- array('obj_id1' => $obj_id1,
- 'obj_id2' => $obj_id2,
- 'no_data' => $no_data));
- }
-
- /**
- * Get the properties that you have set for an app.
- *
- * @param properties List of properties names to fetch
- *
- * @return array A map from property name to value
- */
- public function admin_getAppProperties($properties) {
- return json_decode(
- $this->call_method('facebook.admin.getAppProperties',
- array('properties' => json_encode($properties))), true);
- }
-
- /**
- * Set properties for an app.
- *
- * @param properties A map from property names to values
- *
- * @return bool true on success
- */
- public function admin_setAppProperties($properties) {
- return $this->call_method('facebook.admin.setAppProperties',
- array('properties' => json_encode($properties)));
- }
-
- /**
- * Returns the allocation limit value for a specified integration point name
- * Integration point names are defined in lib/api/karma/constants.php in the
- * limit_map.
- *
- * @param string $integration_point_name Name of an integration point
- * (see developer wiki for list).
- * @param int $uid Specific user to check the limit.
- *
- * @return int Integration point allocation value
- */
- public function &admin_getAllocation($integration_point_name, $uid=null) {
- return $this->call_method('facebook.admin.getAllocation',
- array('integration_point_name' => $integration_point_name,
- 'uid' => $uid));
- }
-
- /**
- * Returns values for the specified metrics for the current application, in
- * the given time range. The metrics are collected for fixed-length periods,
- * and the times represent midnight at the end of each period.
- *
- * @param start_time unix time for the start of the range
- * @param end_time unix time for the end of the range
- * @param period number of seconds in the desired period
- * @param metrics list of metrics to look up
- *
- * @return array A map of the names and values for those metrics
- */
- public function &admin_getMetrics($start_time, $end_time, $period, $metrics) {
- return $this->call_method('admin.getMetrics',
- array('start_time' => $start_time,
- 'end_time' => $end_time,
- 'period' => $period,
- 'metrics' => json_encode($metrics)));
- }
-
- /**
- * Sets application restriction info.
- *
- * Applications can restrict themselves to only a limited user demographic
- * based on users' age and/or location or based on static predefined types
- * specified by facebook for specifying diff age restriction for diff
- * locations.
- *
- * @param array $restriction_info The age restriction settings to set.
- *
- * @return bool true on success
- */
- public function admin_setRestrictionInfo($restriction_info = null) {
- $restriction_str = null;
- if (!empty($restriction_info)) {
- $restriction_str = json_encode($restriction_info);
- }
- return $this->call_method('admin.setRestrictionInfo',
- array('restriction_str' => $restriction_str));
- }
-
- /**
- * Gets application restriction info.
- *
- * Applications can restrict themselves to only a limited user demographic
- * based on users' age and/or location or based on static predefined types
- * specified by facebook for specifying diff age restriction for diff
- * locations.
- *
- * @return array The age restriction settings for this application.
- */
- public function admin_getRestrictionInfo() {
- return json_decode(
- $this->call_method('admin.getRestrictionInfo'),
- true);
- }
-
-
- /**
- * Bans a list of users from the app. Banned users can't
- * access the app's canvas page and forums.
- *
- * @param array $uids an array of user ids
- * @return bool true on success
- */
- public function admin_banUsers($uids) {
- return $this->call_method(
- 'admin.banUsers', array('uids' => json_encode($uids)));
- }
-
- /**
- * Unban users that have been previously banned with
- * admin_banUsers().
- *
- * @param array $uids an array of user ids
- * @return bool true on success
- */
- public function admin_unbanUsers($uids) {
- return $this->call_method(
- 'admin.unbanUsers', array('uids' => json_encode($uids)));
- }
-
- /**
- * Gets the list of users that have been banned from the application.
- * $uids is an optional parameter that filters the result with the list
- * of provided user ids. If $uids is provided,
- * only banned user ids that are contained in $uids are returned.
- *
- * @param array $uids an array of user ids to filter by
- * @return bool true on success
- */
-
- public function admin_getBannedUsers($uids = null) {
- return $this->call_method(
- 'admin.getBannedUsers',
- array('uids' => $uids ? json_encode($uids) : null));
- }
-
-
- /* UTILITY FUNCTIONS */
-
- /**
- * Calls the specified normal POST method with the specified parameters.
- *
- * @param string $method Name of the Facebook method to invoke
- * @param array $params A map of param names => param values
- *
- * @return mixed Result of method call; this returns a reference to support
- * 'delayed returns' when in a batch context.
- * See: http://wiki.developers.facebook.com/index.php/Using_batching_API
- */
- public function &call_method($method, $params = array()) {
- if ($this->format) {
- $params['format'] = $this->format;
- }
- if (!$this->pending_batch()) {
- if ($this->call_as_apikey) {
- $params['call_as_apikey'] = $this->call_as_apikey;
- }
- $data = $this->post_request($method, $params);
- $result = $this->convert_result($data, $method, $params);
- if (is_array($result) && isset($result['error_code'])) {
- throw new FacebookRestClientException($result['error_msg'],
- $result['error_code']);
- }
- }
- else {
- $result = null;
- $batch_item = array('m' => $method, 'p' => $params, 'r' => & $result);
- $this->batch_queue[] = $batch_item;
- }
-
- return $result;
- }
-
- protected function convert_result($data, $method, $params) {
- $is_xml = (empty($params['format']) ||
- strtolower($params['format']) != 'json');
- return ($is_xml) ? $this->convert_xml_to_result($data, $method, $params)
- : json_decode($data, true);
- }
-
- /**
- * Change the response format
- *
- * @param string $format The response format (json, xml)
- */
- public function setFormat($format) {
- $this->format = $format;
- return $this;
- }
-
- /**
- * get the current response serialization format
- *
- * @return string 'xml', 'json', or null (which means 'xml')
- */
- public function getFormat() {
- return $this->format;
- }
-
- /**
- * Calls the specified file-upload POST method with the specified parameters
- *
- * @param string $method Name of the Facebook method to invoke
- * @param array $params A map of param names => param values
- * @param string $file A path to the file to upload (required)
- *
- * @return array A dictionary representing the response.
- */
- public function call_upload_method($method, $params, $file, $server_addr = null) {
- if (!$this->pending_batch()) {
- if (!file_exists($file)) {
- $code =
- FacebookAPIErrorCodes::API_EC_PARAM;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- if ($this->format) {
- $params['format'] = $this->format;
- }
- $data = $this->post_upload_request($method,
- $params,
- $file,
- $server_addr);
- $result = $this->convert_result($data, $method, $params);
-
- if (is_array($result) && isset($result['error_code'])) {
- throw new FacebookRestClientException($result['error_msg'],
- $result['error_code']);
- }
- }
- else {
- $code =
- FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- return $result;
- }
-
- protected function convert_xml_to_result($xml, $method, $params) {
- $sxml = simplexml_load_string($xml);
- $result = self::convert_simplexml_to_array($sxml);
-
- if (!empty($GLOBALS['facebook_config']['debug'])) {
- // output the raw xml and its corresponding php object, for debugging:
- print '<div style="margin: 10px 30px; padding: 5px; border: 2px solid black; background: gray; color: white; font-size: 12px; font-weight: bold;">';
- $this->cur_id++;
- print $this->cur_id . ': Called ' . $method . ', show ' .
- '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'params\');">Params</a> | '.
- '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'xml\');">XML</a> | '.
- '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'sxml\');">SXML</a> | '.
- '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'php\');">PHP</a>';
- print '<pre id="params'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($params, true).'</pre>';
- print '<pre id="xml'.$this->cur_id.'" style="display: none; overflow: auto;">'.htmlspecialchars($xml).'</pre>';
- print '<pre id="php'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($result, true).'</pre>';
- print '<pre id="sxml'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($sxml, true).'</pre>';
- print '</div>';
- }
- return $result;
- }
-
- protected function finalize_params($method, $params) {
- list($get, $post) = $this->add_standard_params($method, $params);
- // we need to do this before signing the params
- $this->convert_array_values_to_json($post);
- $post['sig'] = Facebook::generate_sig(array_merge($get, $post),
- $this->secret);
- return array($get, $post);
- }
-
- private function convert_array_values_to_json(&$params) {
- foreach ($params as $key => &$val) {
- if (is_array($val)) {
- $val = json_encode($val);
- }
- }
- }
-
- /**
- * Add the generally required params to our request.
- * Params method, api_key, and v should be sent over as get.
- */
- private function add_standard_params($method, $params) {
- $post = $params;
- $get = array();
- if ($this->call_as_apikey) {
- $get['call_as_apikey'] = $this->call_as_apikey;
- }
- $get['method'] = $method;
- $get['session_key'] = $this->session_key;
- $get['api_key'] = $this->api_key;
- $post['call_id'] = microtime(true);
- if ($post['call_id'] <= $this->last_call_id) {
- $post['call_id'] = $this->last_call_id + 0.001;
- }
- $this->last_call_id = $post['call_id'];
- if (isset($post['v'])) {
- $get['v'] = $post['v'];
- unset($post['v']);
- } else {
- $get['v'] = '1.0';
- }
- if (isset($this->use_ssl_resources) &&
- $this->use_ssl_resources) {
- $post['return_ssl_resources'] = true;
- }
- return array($get, $post);
- }
-
- private function create_url_string($params) {
- $post_params = array();
- foreach ($params as $key => &$val) {
- $post_params[] = $key.'='.urlencode($val);
- }
- return implode('&', $post_params);
- }
-
- private function run_multipart_http_transaction($method, $params, $file, $server_addr) {
-
- // the format of this message is specified in RFC1867/RFC1341.
- // we add twenty pseudo-random digits to the end of the boundary string.
- $boundary = '--------------------------FbMuLtIpArT' .
- sprintf("%010d", mt_rand()) .
- sprintf("%010d", mt_rand());
- $content_type = 'multipart/form-data; boundary=' . $boundary;
- // within the message, we prepend two extra hyphens.
- $delimiter = '--' . $boundary;
- $close_delimiter = $delimiter . '--';
- $content_lines = array();
- foreach ($params as $key => &$val) {
- $content_lines[] = $delimiter;
- $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"';
- $content_lines[] = '';
- $content_lines[] = $val;
- }
- // now add the file data
- $content_lines[] = $delimiter;
- $content_lines[] =
- 'Content-Disposition: form-data; filename="' . $file . '"';
- $content_lines[] = 'Content-Type: application/octet-stream';
- $content_lines[] = '';
- $content_lines[] = file_get_contents($file);
- $content_lines[] = $close_delimiter;
- $content_lines[] = '';
- $content = implode("\r\n", $content_lines);
- return $this->run_http_post_transaction($content_type, $content, $server_addr);
- }
-
- public function post_request($method, $params) {
- list($get, $post) = $this->finalize_params($method, $params);
- $post_string = $this->create_url_string($post);
- $get_string = $this->create_url_string($get);
- $url_with_get = $this->server_addr . '?' . $get_string;
- if ($this->use_curl_if_available && function_exists('curl_init')) {
- $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url_with_get);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
- $result = $this->curl_exec($ch);
- curl_close($ch);
- } else {
- $content_type = 'application/x-www-form-urlencoded';
- $content = $post_string;
- $result = $this->run_http_post_transaction($content_type,
- $content,
- $url_with_get);
- }
- return $result;
- }
-
- /**
- * execute a curl transaction -- this exists mostly so subclasses can add
- * extra options and/or process the response, if they wish.
- *
- * @param resource $ch a curl handle
- */
- protected function curl_exec($ch) {
- $result = curl_exec($ch);
- return $result;
- }
-
- private function post_upload_request($method, $params, $file, $server_addr = null) {
- $server_addr = $server_addr ? $server_addr : $this->server_addr;
- list($get, $post) = $this->finalize_params($method, $params);
- $get_string = $this->create_url_string($get);
- $url_with_get = $server_addr . '?' . $get_string;
- if ($this->use_curl_if_available && function_exists('curl_init')) {
- // prepending '@' causes cURL to upload the file; the key is ignored.
- $post['_file'] = '@' . $file;
- $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url_with_get);
- // this has to come before the POSTFIELDS set!
- curl_setopt($ch, CURLOPT_POST, 1);
- // passing an array gets curl to use the multipart/form-data content type
- curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
- $result = $this->curl_exec($ch);
- curl_close($ch);
- } else {
- $result = $this->run_multipart_http_transaction($method, $post,
- $file, $url_with_get);
- }
- return $result;
- }
-
- private function run_http_post_transaction($content_type, $content, $server_addr) {
-
- $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion();
- $content_length = strlen($content);
- $context =
- array('http' =>
- array('method' => 'POST',
- 'user_agent' => $user_agent,
- 'header' => 'Content-Type: ' . $content_type . "\r\n" .
- 'Content-Length: ' . $content_length,
- 'content' => $content));
- $context_id = stream_context_create($context);
- $sock = fopen($server_addr, 'r', false, $context_id);
-
- $result = '';
- if ($sock) {
- while (!feof($sock)) {
- $result .= fgets($sock, 4096);
- }
- fclose($sock);
- }
- return $result;
- }
-
- public static function convert_simplexml_to_array($sxml) {
- $arr = array();
- if ($sxml) {
- foreach ($sxml as $k => $v) {
- if ($sxml['list']) {
- $arr[] = self::convert_simplexml_to_array($v);
- } else {
- $arr[$k] = self::convert_simplexml_to_array($v);
- }
- }
- }
- if (sizeof($arr) > 0) {
- return $arr;
- } else {
- return (string)$sxml;
- }
- }
-
- protected function get_uid($uid) {
- return $uid ? $uid : $this->user;
- }
-}
-
-
-class FacebookRestClientException extends Exception {
-}
-
-// Supporting methods and values------
-
-/**
- * Error codes and descriptions for the Facebook API.
- */
-
-class FacebookAPIErrorCodes {
-
- const API_EC_SUCCESS = 0;
-
- /*
- * GENERAL ERRORS
- */
- const API_EC_UNKNOWN = 1;
- const API_EC_SERVICE = 2;
- const API_EC_METHOD = 3;
- const API_EC_TOO_MANY_CALLS = 4;
- const API_EC_BAD_IP = 5;
- const API_EC_HOST_API = 6;
- const API_EC_HOST_UP = 7;
- const API_EC_SECURE = 8;
- const API_EC_RATE = 9;
- const API_EC_PERMISSION_DENIED = 10;
- const API_EC_DEPRECATED = 11;
- const API_EC_VERSION = 12;
- const API_EC_INTERNAL_FQL_ERROR = 13;
- const API_EC_HOST_PUP = 14;
-
- /*
- * PARAMETER ERRORS
- */
- const API_EC_PARAM = 100;
- const API_EC_PARAM_API_KEY = 101;
- const API_EC_PARAM_SESSION_KEY = 102;
- const API_EC_PARAM_CALL_ID = 103;
- const API_EC_PARAM_SIGNATURE = 104;
- const API_EC_PARAM_TOO_MANY = 105;
- const API_EC_PARAM_USER_ID = 110;
- const API_EC_PARAM_USER_FIELD = 111;
- const API_EC_PARAM_SOCIAL_FIELD = 112;
- const API_EC_PARAM_EMAIL = 113;
- const API_EC_PARAM_USER_ID_LIST = 114;
- const API_EC_PARAM_FIELD_LIST = 115;
- const API_EC_PARAM_ALBUM_ID = 120;
- const API_EC_PARAM_PHOTO_ID = 121;
- const API_EC_PARAM_FEED_PRIORITY = 130;
- const API_EC_PARAM_CATEGORY = 140;
- const API_EC_PARAM_SUBCATEGORY = 141;
- const API_EC_PARAM_TITLE = 142;
- const API_EC_PARAM_DESCRIPTION = 143;
- const API_EC_PARAM_BAD_JSON = 144;
- const API_EC_PARAM_BAD_EID = 150;
- const API_EC_PARAM_UNKNOWN_CITY = 151;
- const API_EC_PARAM_BAD_PAGE_TYPE = 152;
-
- /*
- * USER PERMISSIONS ERRORS
- */
- const API_EC_PERMISSION = 200;
- const API_EC_PERMISSION_USER = 210;
- const API_EC_PERMISSION_NO_DEVELOPERS = 211;
- const API_EC_PERMISSION_OFFLINE_ACCESS = 212;
- const API_EC_PERMISSION_ALBUM = 220;
- const API_EC_PERMISSION_PHOTO = 221;
- const API_EC_PERMISSION_MESSAGE = 230;
- const API_EC_PERMISSION_OTHER_USER = 240;
- const API_EC_PERMISSION_STATUS_UPDATE = 250;
- const API_EC_PERMISSION_PHOTO_UPLOAD = 260;
- const API_EC_PERMISSION_VIDEO_UPLOAD = 261;
- const API_EC_PERMISSION_SMS = 270;
- const API_EC_PERMISSION_CREATE_LISTING = 280;
- const API_EC_PERMISSION_CREATE_NOTE = 281;
- const API_EC_PERMISSION_SHARE_ITEM = 282;
- const API_EC_PERMISSION_EVENT = 290;
- const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291;
- const API_EC_PERMISSION_LIVEMESSAGE = 292;
- const API_EC_PERMISSION_RSVP_EVENT = 299;
-
- /*
- * DATA EDIT ERRORS
- */
- const API_EC_EDIT = 300;
- const API_EC_EDIT_USER_DATA = 310;
- const API_EC_EDIT_PHOTO = 320;
- const API_EC_EDIT_ALBUM_SIZE = 321;
- const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322;
- const API_EC_EDIT_PHOTO_TAG_PHOTO = 323;
- const API_EC_EDIT_PHOTO_FILE = 324;
- const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325;
- const API_EC_EDIT_PHOTO_TAG_LIMIT = 326;
- const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327;
- const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328;
-
- const API_EC_MALFORMED_MARKUP = 329;
- const API_EC_EDIT_MARKUP = 330;
-
- const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340;
- const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341;
- const API_EC_EDIT_FEED_TITLE_LINK = 342;
- const API_EC_EDIT_FEED_TITLE_LENGTH = 343;
- const API_EC_EDIT_FEED_TITLE_NAME = 344;
- const API_EC_EDIT_FEED_TITLE_BLANK = 345;
- const API_EC_EDIT_FEED_BODY_LENGTH = 346;
- const API_EC_EDIT_FEED_PHOTO_SRC = 347;
- const API_EC_EDIT_FEED_PHOTO_LINK = 348;
-
- const API_EC_EDIT_VIDEO_SIZE = 350;
- const API_EC_EDIT_VIDEO_INVALID_FILE = 351;
- const API_EC_EDIT_VIDEO_INVALID_TYPE = 352;
- const API_EC_EDIT_VIDEO_FILE = 353;
-
- const API_EC_EDIT_FEED_TITLE_ARRAY = 360;
- const API_EC_EDIT_FEED_TITLE_PARAMS = 361;
- const API_EC_EDIT_FEED_BODY_ARRAY = 362;
- const API_EC_EDIT_FEED_BODY_PARAMS = 363;
- const API_EC_EDIT_FEED_PHOTO = 364;
- const API_EC_EDIT_FEED_TEMPLATE = 365;
- const API_EC_EDIT_FEED_TARGET = 366;
- const API_EC_EDIT_FEED_MARKUP = 367;
-
- /**
- * SESSION ERRORS
- */
- const API_EC_SESSION_TIMED_OUT = 450;
- const API_EC_SESSION_METHOD = 451;
- const API_EC_SESSION_INVALID = 452;
- const API_EC_SESSION_REQUIRED = 453;
- const API_EC_SESSION_REQUIRED_FOR_SECRET = 454;
- const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455;
-
-
- /**
- * FQL ERRORS
- */
- const FQL_EC_UNKNOWN_ERROR = 600;
- const FQL_EC_PARSER = 601; // backwards compatibility
- const FQL_EC_PARSER_ERROR = 601;
- const FQL_EC_UNKNOWN_FIELD = 602;
- const FQL_EC_UNKNOWN_TABLE = 603;
- const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility
- const FQL_EC_NO_INDEX = 604;
- const FQL_EC_UNKNOWN_FUNCTION = 605;
- const FQL_EC_INVALID_PARAM = 606;
- const FQL_EC_INVALID_FIELD = 607;
- const FQL_EC_INVALID_SESSION = 608;
- const FQL_EC_UNSUPPORTED_APP_TYPE = 609;
- const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610;
- const FQL_EC_DEPRECATED_TABLE = 611;
- const FQL_EC_EXTENDED_PERMISSION = 612;
- const FQL_EC_RATE_LIMIT_EXCEEDED = 613;
- const FQL_EC_UNRESOLVED_DEPENDENCY = 614;
-
- const API_EC_REF_SET_FAILED = 700;
-
- /**
- * DATA STORE API ERRORS
- */
- const API_EC_DATA_UNKNOWN_ERROR = 800;
- const API_EC_DATA_INVALID_OPERATION = 801;
- const API_EC_DATA_QUOTA_EXCEEDED = 802;
- const API_EC_DATA_OBJECT_NOT_FOUND = 803;
- const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804;
- const API_EC_DATA_DATABASE_ERROR = 805;
- const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806;
- const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807;
- const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808;
- const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809;
- const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810;
- const API_EC_DATA_MALFORMED_ACTION_LINK = 811;
- const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812;
-
- /*
- * APPLICATION INFO ERRORS
- */
- const API_EC_NO_SUCH_APP = 900;
-
- /*
- * BATCH ERRORS
- */
- const API_EC_BATCH_TOO_MANY_ITEMS = 950;
- const API_EC_BATCH_ALREADY_STARTED = 951;
- const API_EC_BATCH_NOT_STARTED = 952;
- const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953;
-
- /*
- * EVENT API ERRORS
- */
- const API_EC_EVENT_INVALID_TIME = 1000;
-
- /*
- * INFO BOX ERRORS
- */
- const API_EC_INFO_NO_INFORMATION = 1050;
- const API_EC_INFO_SET_FAILED = 1051;
-
- /*
- * LIVEMESSAGE API ERRORS
- */
- const API_EC_LIVEMESSAGE_SEND_FAILED = 1100;
- const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101;
- const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102;
-
- /*
- * PAYMENTS API ERRORS
- */
- const API_EC_PAYMENTS_UNKNOWN = 1150;
- const API_EC_PAYMENTS_APP_INVALID = 1151;
- const API_EC_PAYMENTS_DATABASE = 1152;
- const API_EC_PAYMENTS_PERMISSION_DENIED = 1153;
- const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154;
- const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155;
- const API_EC_PAYMENTS_INVALID_ORDER = 1156;
- const API_EC_PAYMENTS_INVALID_PARAM = 1157;
- const API_EC_PAYMENTS_INVALID_OPERATION = 1158;
- const API_EC_PAYMENTS_PAYMENT_FAILED = 1159;
- const API_EC_PAYMENTS_DISABLED = 1160;
-
- /*
- * CONNECT SESSION ERRORS
- */
- const API_EC_CONNECT_FEED_DISABLED = 1300;
-
- /*
- * Platform tag bundles errors
- */
- const API_EC_TAG_BUNDLE_QUOTA = 1400;
-
- /*
- * SHARE
- */
- const API_EC_SHARE_BAD_URL = 1500;
-
- /*
- * NOTES
- */
- const API_EC_NOTE_CANNOT_MODIFY = 1600;
-
- /*
- * COMMENTS
- */
- const API_EC_COMMENTS_UNKNOWN = 1700;
- const API_EC_COMMENTS_POST_TOO_LONG = 1701;
- const API_EC_COMMENTS_DB_DOWN = 1702;
- const API_EC_COMMENTS_INVALID_XID = 1703;
- const API_EC_COMMENTS_INVALID_UID = 1704;
- const API_EC_COMMENTS_INVALID_POST = 1705;
- const API_EC_COMMENTS_INVALID_REMOVE = 1706;
-
- /**
- * This array is no longer maintained; to view the description of an error
- * code, please look at the message element of the API response or visit
- * the developer wiki at http://wiki.developers.facebook.com/.
- */
- public static $api_error_descriptions = array(
- self::API_EC_SUCCESS => 'Success',
- self::API_EC_UNKNOWN => 'An unknown error occurred',
- self::API_EC_SERVICE => 'Service temporarily unavailable',
- self::API_EC_METHOD => 'Unknown method',
- self::API_EC_TOO_MANY_CALLS => 'Application request limit reached',
- self::API_EC_BAD_IP => 'Unauthorized source IP address',
- self::API_EC_PARAM => 'Invalid parameter',
- self::API_EC_PARAM_API_KEY => 'Invalid API key',
- self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
- self::API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous',
- self::API_EC_PARAM_SIGNATURE => 'Incorrect signature',
- self::API_EC_PARAM_USER_ID => 'Invalid user id',
- self::API_EC_PARAM_USER_FIELD => 'Invalid user info field',
- self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
- self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list',
- self::API_EC_PARAM_FIELD_LIST => 'Invalid field list',
- self::API_EC_PARAM_ALBUM_ID => 'Invalid album id',
- self::API_EC_PARAM_BAD_EID => 'Invalid eid',
- self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
- self::API_EC_PERMISSION => 'Permissions error',
- self::API_EC_PERMISSION_USER => 'User not visible',
- self::API_EC_PERMISSION_NO_DEVELOPERS => 'Application has no developers',
- self::API_EC_PERMISSION_ALBUM => 'Album not visible',
- self::API_EC_PERMISSION_PHOTO => 'Photo not visible',
- self::API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event',
- self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
- self::API_EC_EDIT_ALBUM_SIZE => 'Album is full',
- self::FQL_EC_PARSER => 'FQL: Parser Error',
- self::FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field',
- self::FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table',
- self::FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable',
- self::FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function',
- self::FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in',
- self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
- self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
- self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
- self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
- self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
- self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
- self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
- self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch',
- self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode'
- );
-}
diff --git a/extlib/facebook/jsonwrapper/JSON/JSON.php b/extlib/facebook/jsonwrapper/JSON/JSON.php
deleted file mode 100644
index 0cddbddb4..000000000
--- a/extlib/facebook/jsonwrapper/JSON/JSON.php
+++ /dev/null
@@ -1,806 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Converts to and from JSON format.
- *
- * JSON (JavaScript Object Notation) is a lightweight data-interchange
- * format. It is easy for humans to read and write. It is easy for machines
- * to parse and generate. It is based on a subset of the JavaScript
- * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
- * This feature can also be found in Python. JSON is a text format that is
- * completely language independent but uses conventions that are familiar
- * to programmers of the C-family of languages, including C, C++, C#, Java,
- * JavaScript, Perl, TCL, and many others. These properties make JSON an
- * ideal data-interchange language.
- *
- * This package provides a simple encoder and decoder for JSON notation. It
- * is intended for use with client-side Javascript applications that make
- * use of HTTPRequest to perform server communication functions - data can
- * be encoded into JSON notation for use in a client-side javascript, or
- * decoded from incoming Javascript requests. JSON format is native to
- * Javascript, and can be directly eval()'ed with no further parsing
- * overhead
- *
- * All strings should be in ASCII or UTF-8 format!
- *
- * LICENSE: Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met: Redistributions of source code must retain the
- * above copyright notice, this list of conditions and the following
- * disclaimer. Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
- * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
- * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- * @category
- * @package Services_JSON
- * @author Michal Migurski <mike-json@teczno.com>
- * @author Matt Knapp <mdknapp[at]gmail[dot]com>
- * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
- * @copyright 2005 Michal Migurski
- * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
- * @license http://www.opensource.org/licenses/bsd-license.php
- * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
- */
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_SLICE', 1);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_STR', 2);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_ARR', 3);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_OBJ', 4);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_CMT', 5);
-
-/**
- * Behavior switch for Services_JSON::decode()
- */
-define('SERVICES_JSON_LOOSE_TYPE', 16);
-
-/**
- * Behavior switch for Services_JSON::decode()
- */
-define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
-
-/**
- * Converts to and from JSON format.
- *
- * Brief example of use:
- *
- * <code>
- * // create a new instance of Services_JSON
- * $json = new Services_JSON();
- *
- * // convert a complexe value to JSON notation, and send it to the browser
- * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
- * $output = $json->encode($value);
- *
- * print($output);
- * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
- *
- * // accept incoming POST data, assumed to be in JSON notation
- * $input = file_get_contents('php://input', 1000000);
- * $value = $json->decode($input);
- * </code>
- */
-class Services_JSON
-{
- /**
- * constructs a new JSON instance
- *
- * @param int $use object behavior flags; combine with boolean-OR
- *
- * possible values:
- * - SERVICES_JSON_LOOSE_TYPE: loose typing.
- * "{...}" syntax creates associative arrays
- * instead of objects in decode().
- * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
- * Values which can't be encoded (e.g. resources)
- * appear as NULL instead of throwing errors.
- * By default, a deeply-nested resource will
- * bubble up with an error, so all return values
- * from encode() should be checked with isError()
- */
- function Services_JSON($use = 0)
- {
- $this->use = $use;
- }
-
- /**
- * convert a string from one UTF-16 char to one UTF-8 char
- *
- * Normally should be handled by mb_convert_encoding, but
- * provides a slower PHP-only method for installations
- * that lack the multibye string extension.
- *
- * @param string $utf16 UTF-16 character
- * @return string UTF-8 character
- * @access private
- */
- function utf162utf8($utf16)
- {
- // oh please oh please oh please oh please oh please
- if(function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
- }
-
- $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
-
- switch(true) {
- case ((0x7F & $bytes) == $bytes):
- // this case should never be reached, because we are in ASCII range
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x7F & $bytes);
-
- case (0x07FF & $bytes) == $bytes:
- // return a 2-byte UTF-8 character
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0xC0 | (($bytes >> 6) & 0x1F))
- . chr(0x80 | ($bytes & 0x3F));
-
- case (0xFFFF & $bytes) == $bytes:
- // return a 3-byte UTF-8 character
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0xE0 | (($bytes >> 12) & 0x0F))
- . chr(0x80 | (($bytes >> 6) & 0x3F))
- . chr(0x80 | ($bytes & 0x3F));
- }
-
- // ignoring UTF-32 for now, sorry
- return '';
- }
-
- /**
- * convert a string from one UTF-8 char to one UTF-16 char
- *
- * Normally should be handled by mb_convert_encoding, but
- * provides a slower PHP-only method for installations
- * that lack the multibye string extension.
- *
- * @param string $utf8 UTF-8 character
- * @return string UTF-16 character
- * @access private
- */
- function utf82utf16($utf8)
- {
- // oh please oh please oh please oh please oh please
- if(function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
- }
-
- switch(strlen($utf8)) {
- case 1:
- // this case should never be reached, because we are in ASCII range
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return $utf8;
-
- case 2:
- // return a UTF-16 character from a 2-byte UTF-8 char
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x07 & (ord($utf8{0}) >> 2))
- . chr((0xC0 & (ord($utf8{0}) << 6))
- | (0x3F & ord($utf8{1})));
-
- case 3:
- // return a UTF-16 character from a 3-byte UTF-8 char
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr((0xF0 & (ord($utf8{0}) << 4))
- | (0x0F & (ord($utf8{1}) >> 2)))
- . chr((0xC0 & (ord($utf8{1}) << 6))
- | (0x7F & ord($utf8{2})));
- }
-
- // ignoring UTF-32 for now, sorry
- return '';
- }
-
- /**
- * encodes an arbitrary variable into JSON format
- *
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
- * see argument 1 to Services_JSON() above for array-parsing behavior.
- * if var is a strng, note that encode() always expects it
- * to be in ASCII or UTF-8 format!
- *
- * @return mixed JSON string representation of input var or an error if a problem occurs
- * @access public
- */
- function encode($var)
- {
- switch (gettype($var)) {
- case 'boolean':
- return $var ? 'true' : 'false';
-
- case 'NULL':
- return 'null';
-
- case 'integer':
- return (int) $var;
-
- case 'double':
- case 'float':
- return (float) $var;
-
- case 'string':
- // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
- $ascii = '';
- $strlen_var = strlen($var);
-
- /*
- * Iterate over every character in the string,
- * escaping with a slash or encoding to UTF-8 where necessary
- */
- for ($c = 0; $c < $strlen_var; ++$c) {
-
- $ord_var_c = ord($var{$c});
-
- switch (true) {
- case $ord_var_c == 0x08:
- $ascii .= '\b';
- break;
- case $ord_var_c == 0x09:
- $ascii .= '\t';
- break;
- case $ord_var_c == 0x0A:
- $ascii .= '\n';
- break;
- case $ord_var_c == 0x0C:
- $ascii .= '\f';
- break;
- case $ord_var_c == 0x0D:
- $ascii .= '\r';
- break;
-
- case $ord_var_c == 0x22:
- case $ord_var_c == 0x2F:
- case $ord_var_c == 0x5C:
- // double quote, slash, slosh
- $ascii .= '\\'.$var{$c};
- break;
-
- case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
- // characters U-00000000 - U-0000007F (same as ASCII)
- $ascii .= $var{$c};
- break;
-
- case (($ord_var_c & 0xE0) == 0xC0):
- // characters U-00000080 - U-000007FF, mask 110XXXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
- $c += 1;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xF0) == 0xE0):
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}));
- $c += 2;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xF8) == 0xF0):
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}),
- ord($var{$c + 3}));
- $c += 3;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xFC) == 0xF8):
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}),
- ord($var{$c + 3}),
- ord($var{$c + 4}));
- $c += 4;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xFE) == 0xFC):
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}),
- ord($var{$c + 3}),
- ord($var{$c + 4}),
- ord($var{$c + 5}));
- $c += 5;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
- }
- }
-
- return '"'.$ascii.'"';
-
- case 'array':
- /*
- * As per JSON spec if any array key is not an integer
- * we must treat the the whole array as an object. We
- * also try to catch a sparsely populated associative
- * array with numeric keys here because some JS engines
- * will create an array with empty indexes up to
- * max_index which can cause memory issues and because
- * the keys, which may be relevant, will be remapped
- * otherwise.
- *
- * As per the ECMA and JSON specification an object may
- * have any string as a property. Unfortunately due to
- * a hole in the ECMA specification if the key is a
- * ECMA reserved word or starts with a digit the
- * parameter is only accessible using ECMAScript's
- * bracket notation.
- */
-
- // treat as a JSON object
- if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
- $properties = array_map(array($this, 'name_value'),
- array_keys($var),
- array_values($var));
-
- foreach($properties as $property) {
- if(Services_JSON::isError($property)) {
- return $property;
- }
- }
-
- return '{' . join(',', $properties) . '}';
- }
-
- // treat it like a regular array
- $elements = array_map(array($this, 'encode'), $var);
-
- foreach($elements as $element) {
- if(Services_JSON::isError($element)) {
- return $element;
- }
- }
-
- return '[' . join(',', $elements) . ']';
-
- case 'object':
- $vars = get_object_vars($var);
-
- $properties = array_map(array($this, 'name_value'),
- array_keys($vars),
- array_values($vars));
-
- foreach($properties as $property) {
- if(Services_JSON::isError($property)) {
- return $property;
- }
- }
-
- return '{' . join(',', $properties) . '}';
-
- default:
- return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
- ? 'null'
- : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
- }
- }
-
- /**
- * array-walking function for use in generating JSON-formatted name-value pairs
- *
- * @param string $name name of key to use
- * @param mixed $value reference to an array element to be encoded
- *
- * @return string JSON-formatted name-value pair, like '"name":value'
- * @access private
- */
- function name_value($name, $value)
- {
- $encoded_value = $this->encode($value);
-
- if(Services_JSON::isError($encoded_value)) {
- return $encoded_value;
- }
-
- return $this->encode(strval($name)) . ':' . $encoded_value;
- }
-
- /**
- * reduce a string by removing leading and trailing comments and whitespace
- *
- * @param $str string string value to strip of comments and whitespace
- *
- * @return string string value stripped of comments and whitespace
- * @access private
- */
- function reduce_string($str)
- {
- $str = preg_replace(array(
-
- // eliminate single line comments in '// ...' form
- '#^\s*//(.+)$#m',
-
- // eliminate multi-line comments in '/* ... */' form, at start of string
- '#^\s*/\*(.+)\*/#Us',
-
- // eliminate multi-line comments in '/* ... */' form, at end of string
- '#/\*(.+)\*/\s*$#Us'
-
- ), '', $str);
-
- // eliminate extraneous space
- return trim($str);
- }
-
- /**
- * decodes a JSON string into appropriate variable
- *
- * @param string $str JSON-formatted string
- *
- * @return mixed number, boolean, string, array, or object
- * corresponding to given JSON input string.
- * See argument 1 to Services_JSON() above for object-output behavior.
- * Note that decode() always returns strings
- * in ASCII or UTF-8 format!
- * @access public
- */
- function decode($str)
- {
- $str = $this->reduce_string($str);
-
- switch (strtolower($str)) {
- case 'true':
- return true;
-
- case 'false':
- return false;
-
- case 'null':
- return null;
-
- default:
- $m = array();
-
- if (is_numeric($str)) {
- // Lookie-loo, it's a number
-
- // This would work on its own, but I'm trying to be
- // good about returning integers where appropriate:
- // return (float)$str;
-
- // Return float or int, as appropriate
- return ((float)$str == (integer)$str)
- ? (integer)$str
- : (float)$str;
-
- } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
- // STRINGS RETURNED IN UTF-8 FORMAT
- $delim = substr($str, 0, 1);
- $chrs = substr($str, 1, -1);
- $utf8 = '';
- $strlen_chrs = strlen($chrs);
-
- for ($c = 0; $c < $strlen_chrs; ++$c) {
-
- $substr_chrs_c_2 = substr($chrs, $c, 2);
- $ord_chrs_c = ord($chrs{$c});
-
- switch (true) {
- case $substr_chrs_c_2 == '\b':
- $utf8 .= chr(0x08);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\t':
- $utf8 .= chr(0x09);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\n':
- $utf8 .= chr(0x0A);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\f':
- $utf8 .= chr(0x0C);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\r':
- $utf8 .= chr(0x0D);
- ++$c;
- break;
-
- case $substr_chrs_c_2 == '\\"':
- case $substr_chrs_c_2 == '\\\'':
- case $substr_chrs_c_2 == '\\\\':
- case $substr_chrs_c_2 == '\\/':
- if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
- ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
- $utf8 .= $chrs{++$c};
- }
- break;
-
- case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
- // single, escaped unicode character
- $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
- . chr(hexdec(substr($chrs, ($c + 4), 2)));
- $utf8 .= $this->utf162utf8($utf16);
- $c += 5;
- break;
-
- case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
- $utf8 .= $chrs{$c};
- break;
-
- case ($ord_chrs_c & 0xE0) == 0xC0:
- // characters U-00000080 - U-000007FF, mask 110XXXXX
- //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 2);
- ++$c;
- break;
-
- case ($ord_chrs_c & 0xF0) == 0xE0:
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 3);
- $c += 2;
- break;
-
- case ($ord_chrs_c & 0xF8) == 0xF0:
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 4);
- $c += 3;
- break;
-
- case ($ord_chrs_c & 0xFC) == 0xF8:
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 5);
- $c += 4;
- break;
-
- case ($ord_chrs_c & 0xFE) == 0xFC:
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 6);
- $c += 5;
- break;
-
- }
-
- }
-
- return $utf8;
-
- } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
- // array, or object notation
-
- if ($str{0} == '[') {
- $stk = array(SERVICES_JSON_IN_ARR);
- $arr = array();
- } else {
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $stk = array(SERVICES_JSON_IN_OBJ);
- $obj = array();
- } else {
- $stk = array(SERVICES_JSON_IN_OBJ);
- $obj = new stdClass();
- }
- }
-
- array_push($stk, array('what' => SERVICES_JSON_SLICE,
- 'where' => 0,
- 'delim' => false));
-
- $chrs = substr($str, 1, -1);
- $chrs = $this->reduce_string($chrs);
-
- if ($chrs == '') {
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- return $arr;
-
- } else {
- return $obj;
-
- }
- }
-
- //print("\nparsing {$chrs}\n");
-
- $strlen_chrs = strlen($chrs);
-
- for ($c = 0; $c <= $strlen_chrs; ++$c) {
-
- $top = end($stk);
- $substr_chrs_c_2 = substr($chrs, $c, 2);
-
- if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
- // found a comma that is not inside a string, array, etc.,
- // OR we've reached the end of the character list
- $slice = substr($chrs, $top['where'], ($c - $top['where']));
- array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
- //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- // we are in an array, so just push an element onto the stack
- array_push($arr, $this->decode($slice));
-
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
- // we are in an object, so figure
- // out the property name and set an
- // element in an associative array,
- // for now
- $parts = array();
-
- if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
- // "name":value pair
- $key = $this->decode($parts[1]);
- $val = $this->decode($parts[2]);
-
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $obj[$key] = $val;
- } else {
- $obj->$key = $val;
- }
- } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
- // name:value pair, where name is unquoted
- $key = $parts[1];
- $val = $this->decode($parts[2]);
-
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $obj[$key] = $val;
- } else {
- $obj->$key = $val;
- }
- }
-
- }
-
- } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
- // found a quote, and we are not inside a string
- array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
- //print("Found start of string at {$c}\n");
-
- } elseif (($chrs{$c} == $top['delim']) &&
- ($top['what'] == SERVICES_JSON_IN_STR) &&
- ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
- // found a quote, we're in a string, and it's not escaped
- // we know that it's not escaped becase there is _not_ an
- // odd number of backslashes at the end of the string so far
- array_pop($stk);
- //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
-
- } elseif (($chrs{$c} == '[') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a left-bracket, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
- //print("Found start of array at {$c}\n");
-
- } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
- // found a right-bracket, and we're in an array
- array_pop($stk);
- //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- } elseif (($chrs{$c} == '{') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a left-brace, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
- //print("Found start of object at {$c}\n");
-
- } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
- // found a right-brace, and we're in an object
- array_pop($stk);
- //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- } elseif (($substr_chrs_c_2 == '/*') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a comment start, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
- $c++;
- //print("Found start of comment at {$c}\n");
-
- } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
- // found a comment end, and we're in one now
- array_pop($stk);
- $c++;
-
- for ($i = $top['where']; $i <= $c; ++$i)
- $chrs = substr_replace($chrs, ' ', $i, 1);
-
- //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- }
-
- }
-
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- return $arr;
-
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
- return $obj;
-
- }
-
- }
- }
- }
-
- /**
- * @todo Ultimately, this should just call PEAR::isError()
- */
- function isError($data, $code = null)
- {
- if (class_exists('pear')) {
- return PEAR::isError($data, $code);
- } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
- is_subclass_of($data, 'services_json_error'))) {
- return true;
- }
-
- return false;
- }
-}
-
-if (class_exists('PEAR_Error')) {
-
- class Services_JSON_Error extends PEAR_Error
- {
- function Services_JSON_Error($message = 'unknown error', $code = null,
- $mode = null, $options = null, $userinfo = null)
- {
- parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
- }
- }
-
-} else {
-
- /**
- * @todo Ultimately, this class shall be descended from PEAR_Error
- */
- class Services_JSON_Error
- {
- function Services_JSON_Error($message = 'unknown error', $code = null,
- $mode = null, $options = null, $userinfo = null)
- {
-
- }
- }
-
-}
-
-?>
diff --git a/extlib/facebook/jsonwrapper/JSON/LICENSE b/extlib/facebook/jsonwrapper/JSON/LICENSE
deleted file mode 100644
index 4ae6bef55..000000000
--- a/extlib/facebook/jsonwrapper/JSON/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-Redistributions of source code must retain the above copyright notice,
-this list of conditions and the following disclaimer.
-
-Redistributions in binary form must reproduce the above copyright
-notice, this list of conditions and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
-NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/extlib/facebook/jsonwrapper/jsonwrapper.php b/extlib/facebook/jsonwrapper/jsonwrapper.php
deleted file mode 100644
index 29509deba..000000000
--- a/extlib/facebook/jsonwrapper/jsonwrapper.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-# In PHP 5.2 or higher we don't need to bring this in
-if (!function_exists('json_encode')) {
- require_once 'jsonwrapper_inner.php';
-}
-?>
diff --git a/extlib/facebook/jsonwrapper/jsonwrapper_inner.php b/extlib/facebook/jsonwrapper/jsonwrapper_inner.php
deleted file mode 100644
index 36a3f2863..000000000
--- a/extlib/facebook/jsonwrapper/jsonwrapper_inner.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-require_once 'JSON/JSON.php';
-
-function json_encode($arg)
-{
- global $services_json;
- if (!isset($services_json)) {
- $services_json = new Services_JSON();
- }
- return $services_json->encode($arg);
-}
-
-function json_decode($arg)
-{
- global $services_json;
- if (!isset($services_json)) {
- $services_json = new Services_JSON();
- }
- return $services_json->decode($arg);
-}
-
-?>
diff --git a/extlib/libomb/base_url_xrds_mapper.php b/extlib/libomb/base_url_xrds_mapper.php
new file mode 100755
index 000000000..645459583
--- /dev/null
+++ b/extlib/libomb/base_url_xrds_mapper.php
@@ -0,0 +1,51 @@
+<?php
+
+require_once 'xrds_mapper.php';
+require_once 'constants.php';
+
+/**
+ * Map XRDS actions to URLs using base URLs.
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Base_URL_XRDS_Mapper implements OMB_XRDS_Mapper {
+
+ protected $urls;
+
+ public function __construct($oauth_base, $omb_base) {
+ $this->urls = array(
+ OAUTH_ENDPOINT_REQUEST => $oauth_base . 'requesttoken',
+ OAUTH_ENDPOINT_AUTHORIZE => $oauth_base . 'userauthorization',
+ OAUTH_ENDPOINT_ACCESS => $oauth_base . 'accesstoken',
+ OMB_ENDPOINT_POSTNOTICE => $omb_base . 'postnotice',
+ OMB_ENDPOINT_UPDATEPROFILE => $omb_base . 'updateprofile');
+ }
+
+ public function getURL($action) {
+ return $this->urls[$action];
+ }
+}
+?>
diff --git a/extlib/libomb/constants.php b/extlib/libomb/constants.php
new file mode 100644
index 000000000..a097443ac
--- /dev/null
+++ b/extlib/libomb/constants.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Constants for libomb
+ *
+ * This file contains constant definitions for libomb. The defined constants
+ * are service and namespace URIs for OAuth and OMB as used in XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+/**
+ * The OMB constants.
+ **/
+
+define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
+
+/* The OMB version supported by this libomb version. */
+define('OMB_VERSION', OMB_VERSION_01);
+
+define('OMB_ENDPOINT_UPDATEPROFILE', OMB_VERSION . '/updateProfile');
+define('OMB_ENDPOINT_POSTNOTICE', OMB_VERSION . '/postNotice');
+
+/**
+ * The OAuth constants.
+ **/
+
+define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
+
+define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
+define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
+define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
+define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
+
+define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
+define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
+
+define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
+
+define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
+?>
diff --git a/extlib/libomb/datastore.php b/extlib/libomb/datastore.php
new file mode 100755
index 000000000..ab52de547
--- /dev/null
+++ b/extlib/libomb/datastore.php
@@ -0,0 +1,200 @@
+<?php
+
+require_once 'OAuth.php';
+
+/**
+ * Data access interface
+ *
+ * This interface specifies data access methods libomb needs. It should be
+ * implemented by libomb users. OMB_Datastore is libomb’s main interface to the
+ * application’s data. Objects corresponding to this interface are used in
+ * OMB_Service_Provider and OMB_Service_Consumer.
+ *
+ * Note that it’s implemented as a class since OAuthDataStore is as well a
+ * class, though only declaring methods.
+ *
+ * OMB_Datastore extends OAuthDataStore with two OAuth-related methods for token
+ * revoking and authorizing and all OMB-related methods.
+ * Refer to OAuth.php for a complete specification of OAuth-related methods.
+ *
+ * It is the user’s duty to signal and handle errors. libomb does not check
+ * return values nor handle exceptions. It is suggested to use exceptions.
+ * Note that lookup_token and getProfile return null if the requested object
+ * is not available. This is NOT an error and should not raise an exception.
+ * Same applies for lookup_nonce which returns a boolean value. These methods
+ * may nevertheless throw an exception, for example in case of a storage errors.
+ *
+ * Most of the parameters passed to these methods are unescaped and unverified
+ * user input. Therefore they should be handled with extra care to avoid
+ * security problems like SQL injections.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Datastore extends OAuthDataStore {
+
+ /*********
+ * OAUTH *
+ *********/
+
+ /**
+ * Revoke specified OAuth token
+ *
+ * Revokes the authorization token specified by $token_key.
+ * Throws exceptions in case of error.
+ *
+ * @param string $token_key The key of the token to be revoked
+ *
+ * @access public
+ **/
+ public function revoke_token($token_key) {
+ throw new Exception();
+ }
+
+ /**
+ * Authorize specified OAuth token
+ *
+ * Authorizes the authorization token specified by $token_key.
+ * Throws exceptions in case of error.
+ *
+ * @param string $token_key The key of the token to be authorized
+ *
+ * @access public
+ **/
+ public function authorize_token($token_key) {
+ throw new Exception();
+ }
+
+ /*********
+ * OMB *
+ *********/
+
+ /**
+ * Get profile by identifying URI
+ *
+ * Returns an OMB_Profile object representing the OMB profile identified by
+ * $identifier_uri.
+ * Returns null if there is no such OMB profile.
+ * Throws exceptions in case of other error.
+ *
+ * @param string $identifier_uri The OMB identifier URI specifying the
+ * requested profile
+ *
+ * @access public
+ *
+ * @return OMB_Profile The corresponding profile
+ **/
+ public function getProfile($identifier_uri) {
+ throw new Exception();
+ }
+
+ /**
+ * Save passed profile
+ *
+ * Stores the OMB profile $profile. Overwrites an existing entry.
+ * Throws exceptions in case of error.
+ *
+ * @param OMB_Profile $profile The OMB profile which should be saved
+ *
+ * @access public
+ **/
+ public function saveProfile($profile) {
+ throw new Exception();
+ }
+
+ /**
+ * Save passed notice
+ *
+ * Stores the OMB notice $notice. The datastore may change the passed notice.
+ * This might by neccessary for URIs depending on a database key. Note that
+ * it is the user’s duty to present a mechanism for his OMB_Datastore to
+ * appropriately change his OMB_Notice. TODO: Ugly.
+ * Throws exceptions in case of error.
+ *
+ * @param OMB_Notice $notice The OMB notice which should be saved
+ *
+ * @access public
+ **/
+ public function saveNotice(&$notice) {
+ throw new Exception();
+ }
+
+ /**
+ * Get subscriptions of a given profile
+ *
+ * Returns an array containing subscription informations for the specified
+ * profile. Every array entry should in turn be an array with keys
+ * 'uri´: The identifier URI of the subscriber
+ * 'token´: The subscribe token
+ * 'secret´: The secret token
+ * Throws exceptions in case of error.
+ *
+ * @param string $subscribed_user_uri The OMB identifier URI specifying the
+ * subscribed profile
+ *
+ * @access public
+ *
+ * @return mixed An array containing the subscriptions or 0 if no
+ * subscription has been found.
+ **/
+ public function getSubscriptions($subscribed_user_uri) {
+ throw new Exception();
+ }
+
+ /**
+ * Delete a subscription
+ *
+ * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
+ * Throws exceptions in case of error.
+ *
+ * @param string $subscriber_uri The OMB identifier URI specifying the
+ * subscribing profile
+ *
+ * @param string $subscribed_user_uri The OMB identifier URI specifying the
+ * subscribed profile
+ *
+ * @access public
+ **/
+ public function deleteSubscription($subscriber_uri, $subscribed_user_uri) {
+ throw new Exception();
+ }
+
+ /**
+ * Save a subscription
+ *
+ * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
+ * Throws exceptions in case of error.
+ *
+ * @param string $subscriber_uri The OMB identifier URI specifying
+ * the subscribing profile
+ *
+ * @param string $subscribed_user_uri The OMB identifier URI specifying
+ * the subscribed profile
+ * @param OAuthToken $token The access token
+ *
+ * @access public
+ **/
+ public function saveSubscription($subscriber_uri, $subscribed_user_uri,
+ $token) {
+ throw new Exception();
+ }
+}
+?>
diff --git a/extlib/libomb/helper.php b/extlib/libomb/helper.php
new file mode 100644
index 000000000..a1f21f268
--- /dev/null
+++ b/extlib/libomb/helper.php
@@ -0,0 +1,99 @@
+<?php
+
+require_once 'Validate.php';
+
+/**
+ * Helper functions for libomb
+ *
+ * This file contains helper functions for libomb.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Helper {
+
+ /**
+ * Non-scalar constants
+ *
+ * The set of OMB and OAuth Services an OMB Server has to implement.
+ */
+
+ public static $OMB_SERVICES =
+ array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
+ public static $OAUTH_SERVICES =
+ array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, OAUTH_ENDPOINT_ACCESS);
+
+ /**
+ * Validate URL
+ *
+ * Basic URL validation. Currently http, https, ftp and gopher are supported
+ * schemes.
+ *
+ * @param string $url The URL which is to be validated.
+ *
+ * @return bool Whether URL is valid.
+ *
+ * @access public
+ */
+ public static function validateURL($url) {
+ return Validate::uri($url, array('allowed_schemes' => array('http', 'https',
+ 'gopher', 'ftp')));
+ }
+
+ /**
+ * Validate Media type
+ *
+ * Basic Media type validation. Checks for valid maintype and correct format.
+ *
+ * @param string $mediatype The Media type which is to be validated.
+ *
+ * @return bool Whether media type is valid.
+ *
+ * @access public
+ */
+ public static function validateMediaType($mediatype) {
+ if (0 === preg_match('/^(\w+)\/([\w\d-+.]+)$/', $mediatype, $subtypes)) {
+ return false;
+ }
+ if (!in_array(strtolower($subtypes[1]), array('application', 'audio', 'image',
+ 'message', 'model', 'multipart', 'text', 'video'))) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove escaping from request parameters
+ *
+ * Neutralise the evil effects of magic_quotes_gpc in the current request.
+ * This is used before handing a request off to OAuthRequest::from_request.
+ * Many thanks to Ciaran Gultnieks for this fix.
+ *
+ * @access public
+ */
+ public static function removeMagicQuotesFromRequest() {
+ if(get_magic_quotes_gpc() == 1) {
+ $_POST = array_map('stripslashes', $_POST);
+ $_GET = array_map('stripslashes', $_GET);
+ }
+ }
+}
+?>
diff --git a/extlib/libomb/invalidparameterexception.php b/extlib/libomb/invalidparameterexception.php
new file mode 100755
index 000000000..163e1dd4c
--- /dev/null
+++ b/extlib/libomb/invalidparameterexception.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Exception stating that a passed parameter is invalid
+ *
+ * This exception is raised when a parameter does not obey the OMB standard.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_InvalidParameterException extends Exception {
+ public function __construct($value, $type, $parameter) {
+ parent::__construct("Invalid value $value for parameter $parameter in $type");
+ }
+}
+?>
diff --git a/extlib/libomb/invalidyadisexception.php b/extlib/libomb/invalidyadisexception.php
new file mode 100755
index 000000000..797b7b95b
--- /dev/null
+++ b/extlib/libomb/invalidyadisexception.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Exception stating that a requested url does not resolve to a valid yadis
+ *
+ * This exception is raised when OMB_Service is not able to discover a valid
+ * yadis location with XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_InvalidYadisException extends Exception {
+
+}
+?>
diff --git a/extlib/libomb/notice.php b/extlib/libomb/notice.php
new file mode 100755
index 000000000..9ac36640a
--- /dev/null
+++ b/extlib/libomb/notice.php
@@ -0,0 +1,272 @@
+<?php
+require_once 'invalidparameterexception.php';
+require_once 'Validate.php';
+require_once 'helper.php';
+
+/**
+ * OMB Notice representation
+ *
+ * This class represents an OMB notice.
+ *
+ * Do not call the setters with null values. Instead, if you want to delete a
+ * field, pass an empty string. The getters will return null for empty fields.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Notice {
+ protected $author;
+ protected $uri;
+ protected $content;
+ protected $url;
+ protected $license_url; /* url is an own addition for clarification. */
+ protected $seealso_url; /* url is an own addition for clarification. */
+ protected $seealso_disposition;
+ protected $seealso_mediatype;
+ protected $seealso_license_url; /* url is an addition for clarification. */
+
+ /* The notice as OMB param array. Cached and rebuild on usage.
+ false while outdated. */
+ protected $param_array;
+
+ /**
+ * Constructor for OMB_Notice
+ *
+ * Initializes the OMB_Notice object with author, uri and content.
+ * These parameters are mandatory for postNotice.
+ *
+ * @param object $author An OMB_Profile object representing the author of the
+ * notice.
+ * @param string $uri The notice URI as defined by the OMB. A unique and
+ * unchanging identifier for a notice.
+ * @param string $content The content of the notice. 140 chars recommended,
+ * but there is no limit.
+ *
+ * @access public
+ */
+ public function __construct($author, $uri, $content) {
+ $this->content = $content;
+ if (is_null($author)) {
+ throw new OMB_InvalidParameterException('', 'notice', 'omb_listenee');
+ }
+ $this->author = $author;
+
+ if (!Validate::uri($uri)) {
+ throw new OMB_InvalidParameterException($uri, 'notice', 'omb_notice');
+ }
+ $this->uri = $uri;
+
+ $this->param_array = false;
+ }
+
+ /**
+ * Returns the notice as array
+ *
+ * The method returns an array which contains the whole notice as array. The
+ * array is cached and only rebuilt on changes of the notice.
+ * Empty optional values are not passed.
+ *
+ * @access public
+ * @returns array The notice as parameter array
+ */
+ public function asParameters() {
+ if ($this->param_array !== false) {
+ return $this->param_array;
+ }
+
+ $this->param_array = array(
+ 'omb_notice' => $this->uri,
+ 'omb_notice_content' => $this->content);
+
+ if (!is_null($this->url))
+ $this->param_array['omb_notice_url'] = $this->url;
+
+ if (!is_null($this->license_url))
+ $this->param_array['omb_notice_license'] = $this->license_url;
+
+ if (!is_null($this->seealso_url)) {
+ $this->param_array['omb_seealso'] = $this->seealso_url;
+
+ /* This is actually a free interpretation of the OMB standard. We assume
+ that additional seealso parameters are not of any use if seealso itself
+ is not set. */
+ if (!is_null($this->seealso_disposition))
+ $this->param_array['omb_seealso_disposition'] =
+ $this->seealso_disposition;
+
+ if (!is_null($this->seealso_mediatype))
+ $this->param_array['omb_seealso_mediatype'] = $this->seealso_mediatype;
+
+ if (!is_null($this->seealso_license_url))
+ $this->param_array['omb_seealso_license'] = $this->seealso_license_url;
+ }
+ return $this->param_array;
+ }
+
+ /**
+ * Builds an OMB_Notice object from array
+ *
+ * The method builds an OMB_Notice object from the passed parameters array.
+ * The array MUST provide a notice URI and content. The array fields HAVE TO
+ * be named according to the OMB standard, i. e. omb_notice_* and
+ * omb_seealso_*. Values are handled as not passed if the corresponding array
+ * fields are not set or the empty string.
+ *
+ * @param object $author An OMB_Profile object representing the author of
+ * the notice.
+ * @param string $parameters An array containing the notice parameters.
+ *
+ * @access public
+ *
+ * @returns OMB_Notice The built OMB_Notice.
+ */
+ public static function fromParameters($author, $parameters) {
+ $notice = new OMB_Notice($author, $parameters['omb_notice'],
+ $parameters['omb_notice_content']);
+
+ if (isset($parameters['omb_notice_url'])) {
+ $notice->setURL($parameters['omb_notice_url']);
+ }
+
+ if (isset($parameters['omb_notice_license'])) {
+ $notice->setLicenseURL($parameters['omb_notice_license']);
+ }
+
+ if (isset($parameters['omb_seealso'])) {
+ $notice->setSeealsoURL($parameters['omb_seealso']);
+ }
+
+ if (isset($parameters['omb_seealso_disposition'])) {
+ $notice->setSeealsoDisposition($parameters['omb_seealso_disposition']);
+ }
+
+ if (isset($parameters['omb_seealso_mediatype'])) {
+ $notice->setSeealsoMediatype($parameters['omb_seealso_mediatype']);
+ }
+
+ if (isset($parameters['omb_seealso_license'])) {
+ $notice->setSeealsoLicenseURL($parameters['omb_seealso_license']);
+ }
+ return $notice;
+ }
+
+ public function getAuthor() {
+ return $this->author;
+ }
+
+ public function getIdentifierURI() {
+ return $this->uri;
+ }
+
+ public function getContent() {
+ return $this->content;
+ }
+
+ public function getURL() {
+ return $this->url;
+ }
+
+ public function getLicenseURL() {
+ return $this->license_url;
+ }
+
+ public function getSeealsoURL() {
+ return $this->seealso_url;
+ }
+
+ public function getSeealsoDisposition() {
+ return $this->seealso_disposition;
+ }
+
+ public function getSeealsoMediatype() {
+ return $this->seealso_mediatype;
+ }
+
+ public function getSeealsoLicenseURL() {
+ return $this->seealso_license_url;
+ }
+
+ public function setURL($url) {
+ if ($url === '') {
+ $url = null;
+ } elseif (!OMB_Helper::validateURL($url)) {
+ throw new OMB_InvalidParameterException($url, 'notice', 'omb_notice_url');
+ }
+ $this->url = $url;
+ $this->param_array = false;
+ }
+
+ public function setLicenseURL($license_url) {
+ if ($license_url === '') {
+ $license_url = null;
+ } elseif (!OMB_Helper::validateURL($license_url)) {
+ throw new OMB_InvalidParameterException($license_url, 'notice',
+ 'omb_notice_license');
+ }
+ $this->license_url = $license_url;
+ $this->param_array = false;
+ }
+
+ public function setSeealsoURL($seealso_url) {
+ if ($seealso_url === '') {
+ $seealso_url = null;
+ } elseif (!OMB_Helper::validateURL($seealso_url)) {
+ throw new OMB_InvalidParameterException($seealso_url, 'notice',
+ 'omb_seealso');
+ }
+ $this->seealso_url = $seealso_url;
+ $this->param_array = false;
+ }
+
+ public function setSeealsoDisposition($seealso_disposition) {
+ if ($seealso_disposition === '') {
+ $seealso_disposition = null;
+ } elseif ($seealso_disposition !== 'link' && $seealso_disposition !== 'inline') {
+ throw new OMB_InvalidParameterException($seealso_disposition, 'notice',
+ 'omb_seealso_disposition');
+ }
+ $this->seealso_disposition = $seealso_disposition;
+ $this->param_array = false;
+ }
+
+ public function setSeealsoMediatype($seealso_mediatype) {
+ if ($seealso_mediatype === '') {
+ $seealso_mediatype = null;
+ } elseif (!OMB_Helper::validateMediaType($seealso_mediatype)) {
+ throw new OMB_InvalidParameterException($seealso_mediatype, 'notice',
+ 'omb_seealso_mediatype');
+ }
+ $this->seealso_mediatype = $seealso_mediatype;
+ $this->param_array = false;
+ }
+
+ public function setSeealsoLicenseURL($seealso_license_url) {
+ if ($seealso_license_url === '') {
+ $seealso_license_url = null;
+ } elseif (!OMB_Helper::validateURL($seealso_license_url)) {
+ throw new OMB_InvalidParameterException($seealso_license_url, 'notice',
+ 'omb_seealso_license');
+ }
+ $this->seealso_license_url = $seealso_license_url;
+ $this->param_array = false;
+ }
+}
+?>
diff --git a/extlib/libomb/omb_yadis_xrds.php b/extlib/libomb/omb_yadis_xrds.php
new file mode 100755
index 000000000..89921203b
--- /dev/null
+++ b/extlib/libomb/omb_yadis_xrds.php
@@ -0,0 +1,196 @@
+<?php
+
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'unsupportedserviceexception.php';
+require_once 'invalidyadisexception.php';
+
+/**
+ * OMB XRDS representation
+ *
+ * This class represents a Yadis XRDS file for OMB. It adds some useful methods to
+ * Auth_Yadis_XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Yadis_XRDS extends Auth_Yadis_XRDS {
+
+ protected $fetcher;
+
+ /**
+ * Create an instance from URL
+ *
+ * Constructs an OMB_Yadis_XRDS object from a given URL. A full Yadis
+ * discovery is performed on the URL and the XRDS is parsed.
+ * Throws an OMB_InvalidYadisException when no Yadis is discovered or the
+ * detected XRDS file is broken.
+ *
+ * @param string $url The URL on which Yadis discovery
+ * should be performed on
+ * @param Auth_Yadis_HTTPFetcher $fetcher A fetcher used to get HTTP
+ * resources
+ *
+ * @access public
+ *
+ * @return OMB_Yadis_XRDS The initialized object representing the given
+ * resource
+ **/
+ public static function fromYadisURL($url, $fetcher) {
+ /* Perform a Yadis discovery. */
+ $yadis = Auth_Yadis_Yadis::discover($url, $fetcher);
+ if ($yadis->failed) {
+ throw new OMB_InvalidYadisException($url);
+ }
+
+ /* Parse the XRDS file. */
+ $xrds = OMB_Yadis_XRDS::parseXRDS($yadis->response_text);
+ if ($xrds === null) {
+ throw new OMB_InvalidYadisException($url);
+ }
+ $xrds->fetcher = $fetcher;
+ return $xrds;
+ }
+
+ /**
+ * Get a specific service
+ *
+ * Returns the Auth_Yadis_Service object corresponding to the given service
+ * URI.
+ * Throws an OMB_UnsupportedServiceException if the service is not available.
+ *
+ * @param string $service URI specifier of the requested service
+ *
+ * @access public
+ *
+ * @return Auth_Yadis_Service The object representing the requested service
+ **/
+ public function getService($service) {
+ $match = $this->services(array( create_function('$s',
+ "return in_array('$service', \$s->getTypes());")));
+ if ($match === array()) {
+ throw new OMB_UnsupportedServiceException($service);
+ }
+ return $match[0];
+ }
+
+ /**
+ * Get a specific XRD
+ *
+ * Returns the OMB_Yadis_XRDS object corresponding to the given URI.
+ * Throws an OMB_UnsupportedServiceException if the XRD is not available.
+ * Note that getXRD tries to resolve external XRD parts as well.
+ *
+ * @param string $uri URI specifier of the requested XRD
+ *
+ * @access public
+ *
+ * @return OMB_Yadis_XRDS The object representing the requested XRD
+ **/
+ public function getXRD($uri) {
+ $nexthash = strpos($uri, '#');
+ if ($nexthash !== 0) {
+ if ($nexthash !== false) {
+ $cururi = substr($uri, 0, $nexthash);
+ $nexturi = substr($uri, $nexthash);
+ }
+ return
+ OMB_Yadis_XRDS::fromYadisURL($cururi, $this->fetcher)->getXRD($nexturi);
+ }
+
+ $id = substr($uri, 1);
+ foreach ($this->allXrdNodes as $node) {
+ $attrs = $this->parser->attributes($node);
+ if (array_key_exists('xml:id', $attrs) && $attrs['xml:id'] == $id) {
+ /* Trick the constructor into thinking this is the only node. */
+ $bogus_nodes = array($node);
+ return new OMB_Yadis_XRDS($this->parser, $bogus_nodes);
+ }
+ }
+ throw new OMB_UnsupportedServiceException($uri);
+ }
+
+ /**
+ * Parse an XML string containing a XRDS document
+ *
+ * Parse an XML string (XRDS document) and return either a
+ * Auth_Yadis_XRDS object or null, depending on whether the
+ * XRDS XML is valid.
+ * Copy and paste from parent to select correct constructor.
+ *
+ * @param string $xml_string An XRDS XML string.
+ *
+ * @access public
+ *
+ * @return mixed An instance of OMB_Yadis_XRDS or null,
+ * depending on the validity of $xml_string
+ **/
+
+ public function &parseXRDS($xml_string, $extra_ns_map = null) {
+ $_null = null;
+
+ if (!$xml_string) {
+ return $_null;
+ }
+
+ $parser = Auth_Yadis_getXMLParser();
+
+ $ns_map = Auth_Yadis_getNSMap();
+
+ if ($extra_ns_map && is_array($extra_ns_map)) {
+ $ns_map = array_merge($ns_map, $extra_ns_map);
+ }
+
+ if (!($parser && $parser->init($xml_string, $ns_map))) {
+ return $_null;
+ }
+
+ // Try to get root element.
+ $root = $parser->evalXPath('/xrds:XRDS[1]');
+ if (!$root) {
+ return $_null;
+ }
+
+ if (is_array($root)) {
+ $root = $root[0];
+ }
+
+ $attrs = $parser->attributes($root);
+
+ if (array_key_exists('xmlns:xrd', $attrs) &&
+ $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
+ return $_null;
+ } else if (array_key_exists('xmlns', $attrs) &&
+ preg_match('/xri/', $attrs['xmlns']) &&
+ $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+ return $_null;
+ }
+
+ // Get the last XRD node.
+ $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+ if (!$xrd_nodes) {
+ return $_null;
+ }
+
+ $xrds = new OMB_Yadis_XRDS($parser, $xrd_nodes);
+ return $xrds;
+ }
+}
diff --git a/extlib/libomb/plain_xrds_writer.php b/extlib/libomb/plain_xrds_writer.php
new file mode 100755
index 000000000..b4a6e990b
--- /dev/null
+++ b/extlib/libomb/plain_xrds_writer.php
@@ -0,0 +1,124 @@
+<?php
+
+require_once 'xrds_writer.php';
+
+/**
+ * Write OMB-specific XRDS using XMLWriter.
+ *
+ * This class writes the XRDS file announcing the OMB server. It uses
+ * OMB_XMLWriter, which is a subclass of XMLWriter. An instance of
+ * OMB_Plain_XRDS_Writer should be passed to OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Plain_XRDS_Writer implements OMB_XRDS_Writer {
+ public function writeXRDS($user, $mapper) {
+ header('Content-Type: application/xrds+xml');
+ $xw = new XMLWriter();
+ $xw->openURI('php://output');
+ $xw->setIndent(true);
+
+ $xw->startDocument('1.0', 'UTF-8');
+ $this->writeFullElement($xw, 'XRDS', array('xmlns' => 'xri://$xrds'), array(
+ array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'oauth',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'), array(
+ array('Type', null, 'xri://$xrds*simple'),
+ array('Service', null, array(
+ array('Type', null, OAUTH_ENDPOINT_REQUEST),
+ array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_REQUEST)),
+ array('Type', null, OAUTH_AUTH_HEADER),
+ array('Type', null, OAUTH_POST_BODY),
+ array('Type', null, OAUTH_HMAC_SHA1),
+ array('LocalID', null, $user->getIdentifierURI())
+ )),
+ array('Service', null, array(
+ array('Type', null, OAUTH_ENDPOINT_AUTHORIZE),
+ array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_AUTHORIZE)),
+ array('Type', null, OAUTH_AUTH_HEADER),
+ array('Type', null, OAUTH_POST_BODY),
+ array('Type', null, OAUTH_HMAC_SHA1)
+ )),
+ array('Service', null, array(
+ array('Type', null, OAUTH_ENDPOINT_ACCESS),
+ array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_ACCESS)),
+ array('Type', null, OAUTH_AUTH_HEADER),
+ array('Type', null, OAUTH_POST_BODY),
+ array('Type', null, OAUTH_HMAC_SHA1)
+ )),
+ array('Service', null, array(
+ array('Type', null, OAUTH_ENDPOINT_RESOURCE),
+ array('Type', null, OAUTH_AUTH_HEADER),
+ array('Type', null, OAUTH_POST_BODY),
+ array('Type', null, OAUTH_HMAC_SHA1)
+ ))
+ )),
+ array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'omb',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'), array(
+ array('Type', null, 'xri://$xrds*simple'),
+ array('Service', null, array(
+ array('Type', null, OMB_ENDPOINT_POSTNOTICE),
+ array('URI', null, $mapper->getURL(OMB_ENDPOINT_POSTNOTICE))
+ )),
+ array('Service', null, array(
+ array('Type', null, OMB_ENDPOINT_UPDATEPROFILE),
+ array('URI', null, $mapper->getURL(OMB_ENDPOINT_UPDATEPROFILE))
+ ))
+ )),
+ array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'version' => '2.0'), array(
+ array('Type', null, 'xri://$xrds*simple'),
+ array('Service', null, array(
+ array('Type', null, OAUTH_DISCOVERY),
+ array('URI', null, '#oauth')
+ )),
+ array('Service', null, array(
+ array('Type', null, OMB_VERSION),
+ array('URI', null, '#omb')
+ ))
+ ))
+ ));
+ $xw->endDocument();
+ $xw->flush();
+ }
+
+ public static function writeFullElement($xw, $tag, $attributes, $content) {
+ $xw->startElement($tag);
+ if (!is_null($attributes)) {
+ foreach ($attributes as $name => $value) {
+ $xw->writeAttribute($name, $value);
+ }
+ }
+ if (is_array($content)) {
+ foreach ($content as $values) {
+ OMB_Plain_XRDS_Writer::writeFullElement($xw, $values[0], $values[1], $values[2]);
+ }
+ } else {
+ $xw->text($content);
+ }
+ $xw->fullEndElement();
+ }
+}
+?>
diff --git a/extlib/libomb/profile.php b/extlib/libomb/profile.php
new file mode 100755
index 000000000..13314d3e8
--- /dev/null
+++ b/extlib/libomb/profile.php
@@ -0,0 +1,317 @@
+<?php
+require_once 'invalidparameterexception.php';
+require_once 'Validate.php';
+require_once 'helper.php';
+
+/**
+ * OMB profile representation
+ *
+ * This class represents an OMB profile.
+ *
+ * Do not call the setters with null values. Instead, if you want to delete a
+ * field, pass an empty string. The getters will return null for empty fields.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Profile {
+ protected $identifier_uri;
+ protected $profile_url;
+ protected $nickname;
+ protected $license_url;
+ protected $fullname;
+ protected $homepage;
+ protected $bio;
+ protected $location;
+ protected $avatar_url;
+
+ /* The profile as OMB param array. Cached and rebuild on usage.
+ false while outdated. */
+ protected $param_array;
+
+ /**
+ * Constructor for OMB_Profile
+ *
+ * Initializes the OMB_Profile object with an identifier uri.
+ *
+ * @param string $identifier_uri The profile URI as defined by the OMB. A unique
+ * and unchanging identifier for a profile.
+ *
+ * @access public
+ */
+ public function __construct($identifier_uri) {
+ if (!Validate::uri($identifier_uri)) {
+ throw new OMB_InvalidParameterException($identifier_uri, 'profile',
+ 'omb_listenee or omb_listener');
+ }
+ $this->identifier_uri = $identifier_uri;
+ $this->param_array = false;
+ }
+
+ /**
+ * Returns the profile as array
+ *
+ * The method returns an array which contains the whole profile as array. The
+ * array is cached and only rebuilt on changes of the profile.
+ *
+ * @param bool $force_all Specifies whether empty fields should be added to
+ * the array as well. This is neccessary to clear
+ * fields via updateProfile.
+ *
+ * @param string $prefix The common prefix to the key for all parameters.
+ *
+ * @access public
+ *
+ * @return array The profile as parameter array
+ */
+ public function asParameters($prefix, $force_all = false) {
+ if ($this->param_array === false) {
+ $this->param_array = array('' => $this->identifier_uri);
+
+ if ($force_all || !is_null($this->profile_url)) {
+ $this->param_array['_profile'] = $this->profile_url;
+ }
+
+ if ($force_all || !is_null($this->homepage)) {
+ $this->param_array['_homepage'] = $this->homepage;
+ }
+
+ if ($force_all || !is_null($this->nickname)) {
+ $this->param_array['_nickname'] = $this->nickname;
+ }
+
+ if ($force_all || !is_null($this->license_url)) {
+ $this->param_array['_license'] = $this->license_url;
+ }
+
+ if ($force_all || !is_null($this->fullname)) {
+ $this->param_array['_fullname'] = $this->fullname;
+ }
+
+ if ($force_all || !is_null($this->bio)) {
+ $this->param_array['_bio'] = $this->bio;
+ }
+
+ if ($force_all || !is_null($this->location)) {
+ $this->param_array['_location'] = $this->location;
+ }
+
+ if ($force_all || !is_null($this->avatar_url)) {
+ $this->param_array['_avatar'] = $this->avatar_url;
+ }
+
+ }
+ $ret = array();
+ foreach ($this->param_array as $k => $v) {
+ $ret[$prefix . $k] = $v;
+ }
+ return $ret;
+ }
+
+ /**
+ * Builds an OMB_Profile object from array
+ *
+ * The method builds an OMB_Profile object from the passed parameters array. The
+ * array MUST provide a profile URI. The array fields HAVE TO be named according
+ * to the OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
+ * parameter.
+ *
+ * @param string $parameters An array containing the profile parameters.
+ * @param string $prefix The common prefix of the profile parameter keys.
+ *
+ * @access public
+ *
+ * @returns OMB_Profile The built OMB_Profile.
+ */
+ public static function fromParameters($parameters, $prefix) {
+ if (!isset($parameters[$prefix])) {
+ throw new OMB_InvalidParameterException('', 'profile', $prefix);
+ }
+
+ $profile = new OMB_Profile($parameters[$prefix]);
+ $profile->updateFromParameters($parameters, $prefix);
+ return $profile;
+ }
+
+ /**
+ * Update from array
+ *
+ * Updates from the passed parameters array. The array does not have to
+ * provide a profile URI. The array fields HAVE TO be named according to the
+ * OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
+ * parameter.
+ *
+ * @param string $parameters An array containing the profile parameters.
+ * @param string $prefix The common prefix of the profile parameter keys.
+ *
+ * @access public
+ */
+ public function updateFromParameters($parameters, $prefix) {
+ if (isset($parameters[$prefix.'_profile'])) {
+ $this->setProfileURL($parameters[$prefix.'_profile']);
+ }
+
+ if (isset($parameters[$prefix.'_license'])) {
+ $this->setLicenseURL($parameters[$prefix.'_license']);
+ }
+
+ if (isset($parameters[$prefix.'_nickname'])) {
+ $this->setNickname($parameters[$prefix.'_nickname']);
+ }
+
+ if (isset($parameters[$prefix.'_fullname'])) {
+ $this->setFullname($parameters[$prefix.'_fullname']);
+ }
+
+ if (isset($parameters[$prefix.'_homepage'])) {
+ $this->setHomepage($parameters[$prefix.'_homepage']);
+ }
+
+ if (isset($parameters[$prefix.'_bio'])) {
+ $this->setBio($parameters[$prefix.'_bio']);
+ }
+
+ if (isset($parameters[$prefix.'_location'])) {
+ $this->setLocation($parameters[$prefix.'_location']);
+ }
+
+ if (isset($parameters[$prefix.'_avatar'])) {
+ $this->setAvatarURL($parameters[$prefix.'_avatar']);
+ }
+ }
+
+ public function getIdentifierURI() {
+ return $this->identifier_uri;
+ }
+
+ public function getProfileURL() {
+ return $this->profile_url;
+ }
+
+ public function getHomepage() {
+ return $this->homepage;
+ }
+
+ public function getNickname() {
+ return $this->nickname;
+ }
+
+ public function getLicenseURL() {
+ return $this->license_url;
+ }
+
+ public function getFullname() {
+ return $this->fullname;
+ }
+
+ public function getBio() {
+ return $this->bio;
+ }
+
+ public function getLocation() {
+ return $this->location;
+ }
+
+ public function getAvatarURL() {
+ return $this->avatar_url;
+ }
+
+ public function setProfileURL($profile_url) {
+ if (!OMB_Helper::validateURL($profile_url)) {
+ throw new OMB_InvalidParameterException($profile_url, 'profile',
+ 'omb_listenee_profile or omb_listener_profile');
+ }
+ $this->profile_url = $profile_url;
+ $this->param_array = false;
+ }
+
+ public function setNickname($nickname) {
+ if (!Validate::string($nickname,
+ array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA))) {
+ throw new OMB_InvalidParameterException($nickname, 'profile', 'nickname');
+ }
+
+ $this->nickname = $nickname;
+ $this->param_array = false;
+ }
+
+ public function setLicenseURL($license_url) {
+ if (!OMB_Helper::validateURL($license_url)) {
+ throw new OMB_InvalidParameterException($license_url, 'profile',
+ 'omb_listenee_license or omb_listener_license');
+ }
+ $this->license_url = $license_url;
+ $this->param_array = false;
+ }
+
+ public function setFullname($fullname) {
+ if ($fullname === '') {
+ $fullname = null;
+ } elseif (!Validate::string($fullname, array('max_length' => 255))) {
+ throw new OMB_InvalidParameterException($fullname, 'profile', 'fullname');
+ }
+ $this->fullname = $fullname;
+ $this->param_array = false;
+ }
+
+ public function setHomepage($homepage) {
+ if ($homepage === '') {
+ $homepage = null;
+ }
+ $this->homepage = $homepage;
+ $this->param_array = false;
+ }
+
+ public function setBio($bio) {
+ if ($bio === '') {
+ $bio = null;
+ } elseif (!Validate::string($bio, array('max_length' => 140))) {
+ throw new OMB_InvalidParameterException($bio, 'profile', 'fullname');
+ }
+ $this->bio = $bio;
+ $this->param_array = false;
+ }
+
+ public function setLocation($location) {
+ if ($location === '') {
+ $location = null;
+ } elseif (!Validate::string($location, array('max_length' => 255))) {
+ throw new OMB_InvalidParameterException($location, 'profile', 'fullname');
+ }
+ $this->location = $location;
+ $this->param_array = false;
+ }
+
+ public function setAvatarURL($avatar_url) {
+ if ($avatar_url === '') {
+ $avatar_url = null;
+ } elseif (!OMB_Helper::validateURL($avatar_url)) {
+ throw new OMB_InvalidParameterException($avatar_url, 'profile',
+ 'omb_listenee_avatar or omb_listener_avatar');
+ }
+ $this->avatar_url = $avatar_url;
+ $this->param_array = false;
+ }
+
+}
+?>
diff --git a/extlib/libomb/remoteserviceexception.php b/extlib/libomb/remoteserviceexception.php
new file mode 100755
index 000000000..374d15973
--- /dev/null
+++ b/extlib/libomb/remoteserviceexception.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Exception stating that the remote service had a failure
+ *
+ * This exception is raised when a remote service failed to return a valid
+ * response to a request or send a valid request.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_RemoteServiceException extends Exception {
+ public static function fromYadis($request_uri, $result) {
+ if ($result->status == 200) {
+ $err = 'Got wrong response ' . $result->body;
+ } else {
+ $err = 'Got error code ' . $result->status . ' with response ' . $result->body;
+ }
+ return new OMB_RemoteServiceException($request_uri . ': ' . $err);
+ }
+
+ public static function forRequest($action_uri, $failure) {
+ return new OMB_RemoteServiceException("Handler for $action_uri: " . $failure);
+ }
+}
+?>
diff --git a/extlib/libomb/service_consumer.php b/extlib/libomb/service_consumer.php
new file mode 100755
index 000000000..273fd052e
--- /dev/null
+++ b/extlib/libomb/service_consumer.php
@@ -0,0 +1,430 @@
+<?php
+
+require_once 'constants.php';
+require_once 'Validate.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'OAuth.php';
+require_once 'unsupportedserviceexception.php';
+require_once 'remoteserviceexception.php';
+require_once 'omb_yadis_xrds.php';
+require_once 'helper.php';
+
+/**
+ * OMB service representation
+ *
+ * This class represents a complete remote OMB service. It provides discovery
+ * and execution of the service’s methods.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Service_Consumer {
+ protected $url; /* The service URL */
+ protected $services; /* An array of strings mapping service URI to
+ service URL */
+
+ protected $token; /* An OAuthToken */
+
+ protected $listener_uri; /* The URI identifying the listener, i. e. the
+ remote user. */
+
+ protected $listenee_uri; /* The URI identifying the listenee, i. e. the
+ local user during an auth request. */
+
+ /**
+ * According to OAuth Core 1.0, an user authorization request is no full-blown
+ * OAuth request. nonce, timestamp, consumer_key and signature are not needed
+ * in this step. See http://laconi.ca/trac/ticket/827 for more informations.
+ *
+ * Since Laconica up to version 0.7.2 performs a full OAuth request check, a
+ * correct request would fail.
+ **/
+ public $performLegacyAuthRequest = true;
+
+ /* Helper stuff we are going to need. */
+ protected $fetcher;
+ protected $oauth_consumer;
+ protected $datastore;
+
+ /**
+ * Constructor for OMB_Service_Consumer
+ *
+ * Initializes an OMB_Service_Consumer object representing the OMB service
+ * specified by $service_url. Performs a complete service discovery using
+ * Yadis.
+ * Throws OMB_UnsupportedServiceException if XRDS file does not specify a
+ * complete OMB service.
+ *
+ * @param string $service_url The URL of the service
+ * @param string $consumer_url An URL representing the consumer
+ * @param OMB_Datastore $datastore An instance of a class implementing
+ * OMB_Datastore
+ *
+ * @access public
+ **/
+ public function __construct ($service_url, $consumer_url, $datastore) {
+ $this->url = $service_url;
+ $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $this->datastore = $datastore;
+ $this->oauth_consumer = new OAuthConsumer($consumer_url, '');
+
+ $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
+
+ /* Detect our services. This performs a validation as well, since
+ getService und getXRD throw exceptions on failure. */
+ $this->services = array();
+
+ foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
+ OMB_VERSION => OMB_Helper::$OMB_SERVICES)
+ as $service_root => $targetservices) {
+ $uris = $xrds->getService($service_root)->getURIs();
+ $xrd = $xrds->getXRD($uris[0]);
+ foreach ($targetservices as $targetservice) {
+ $yadis_service = $xrd->getService($targetservice);
+ if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
+ $localid = $yadis_service->getElements('xrd:LocalID');
+ $this->listener_uri = $yadis_service->parser->content($localid[0]);
+ }
+ $uris = $yadis_service->getURIs();
+ $this->services[$targetservice] = $uris[0];
+ }
+ }
+ }
+
+ /**
+ * Get the handler URI for a service
+ *
+ * Returns the URI the remote web service has specified for the given
+ * service.
+ *
+ * @param string $service The URI identifying the service
+ *
+ * @access public
+ *
+ * @return string The service handler URI
+ **/
+ public function getServiceURI($service) {
+ return $this->services[$service];
+ }
+
+ /**
+ * Get the remote user’s URI
+ *
+ * Returns the URI of the remote user, i. e. the listener.
+ *
+ * @access public
+ *
+ * @return string The remote user’s URI
+ **/
+ public function getRemoteUserURI() {
+ return $this->listener_uri;
+ }
+
+ /**
+ * Get the listenee’s URI
+ *
+ * Returns the URI of the user being subscribed to, i. e. the local user.
+ *
+ * @access public
+ *
+ * @return string The local user’s URI
+ **/
+ public function getListeneeURI() {
+ return $this->listenee_uri;
+ }
+
+ /**
+ * Request a request token
+ *
+ * Performs a token request on the service. Returns an OAuthToken on success.
+ * Throws an exception if the request fails.
+ *
+ * @access public
+ *
+ * @return OAuthToken An unauthorized request token
+ **/
+ public function requestToken() {
+ /* Set the token to null just in case the user called setToken. */
+ $this->token = null;
+
+ $result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
+ array('omb_listener' => $this->listener_uri));
+ if ($result->status != 200) {
+ throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
+ $result);
+ }
+ parse_str($result->body, $return);
+ if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
+ throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
+ $result);
+ }
+ $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
+ return $this->token;
+ }
+
+ /**
+ *
+ * Request authorization
+ *
+ * Returns an URL which equals to an authorization request. The end user
+ * should be redirected to this location to perform authorization.
+ * The $finish_url should be a local resource which invokes
+ * OMB_Consumer::finishAuthorization on request.
+ *
+ * @param OMB_Profile $profile An OMB_Profile object representing the
+ * soon-to-be subscribed (i. e. local) user
+ * @param string $finish_url Target location after successful
+ * authorization
+ *
+ * @access public
+ *
+ * @return string An URL representing an authorization request
+ **/
+ public function requestAuthorization($profile, $finish_url) {
+ if ($this->performLegacyAuthRequest) {
+ $params = $profile->asParameters('omb_listenee', false);
+ $params['omb_listener'] = $this->listener_uri;
+ $params['oauth_callback'] = $finish_url;
+
+ $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
+ } else {
+
+ $params = array(
+ 'oauth_callback' => $finish_url,
+ 'oauth_token' => $this->token->key,
+ 'omb_version' => OMB_VERSION,
+ 'omb_listener' => $this->listener_uri);
+
+ $params = array_merge($profile->asParameters('omb_listenee', false). $params);
+
+ /* Build result URL. */
+ $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE];
+ $url .= (strrpos($url, '?') === false ? '?' : '&');
+ foreach ($params as $k => $v) {
+ $url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&';
+ }
+ }
+
+ $this->listenee_uri = $profile->getIdentifierURI();
+
+ return $url;
+ }
+
+ /**
+ * Finish authorization
+ *
+ * Finish the subscription process by converting the received and authorized
+ * request token into an access token. After that, the subscriber’s profile
+ * and the subscription are stored in the database.
+ * Expects an OAuthRequest in query parameters.
+ * Throws exceptions on failure.
+ *
+ * @access public
+ **/
+ public function finishAuthorization() {
+ OMB_Helper::removeMagicQuotesFromRequest();
+ $req = OAuthRequest::from_request();
+ if ($req->get_parameter('oauth_token') !=
+ $this->token->key) {
+ /* That’s not the token I wanted to get authorized. */
+ throw new OAuthException('The authorized token does not equal the ' .
+ 'submitted token.');
+ }
+
+ if ($req->get_parameter('omb_version') != OMB_VERSION) {
+ throw new OMB_RemoteServiceException('The remote service uses an ' .
+ 'unsupported OMB version');
+ }
+
+ /* Construct the profile to validate it. */
+
+ /* Fix OMB bug. Listener URI is not passed. */
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $params = $_POST;
+ } else {
+ $params = $_GET;
+ }
+ $params['omb_listener'] = $this->listener_uri;
+
+ require_once 'profile.php';
+ $listener = OMB_Profile::fromParameters($params, 'omb_listener');
+
+ /* Ask the remote service to convert the authorized request token into an
+ access token. */
+
+ $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
+ if ($result->status != 200) {
+ throw new OAuthException('Could not get access token');
+ }
+
+ parse_str($result->body, $return);
+ if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
+ throw new OAuthException('Could not get access token');
+ }
+ $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
+
+ /* Subscription is finished and valid. Now store the new subscriber and the
+ subscription in the database. */
+
+ $this->datastore->saveProfile($listener);
+ $this->datastore->saveSubscription($this->listener_uri,
+ $this->listenee_uri,
+ $this->token);
+ }
+
+ /**
+ * Return the URI identifying the listener
+ *
+ * Returns the URI for the OMB user who tries to subscribe or already has
+ * subscribed our user. This method is a workaround for a serious OMB flaw:
+ * The Listener URI is not passed in the finishauthorization call.
+ *
+ * @access public
+ *
+ * @return string the listener’s URI
+ **/
+ public function getListenerURI() {
+ return $this->listener_uri;
+ }
+
+ /**
+ * Inform the service about a profile update
+ *
+ * Sends an updated profile to the service.
+ *
+ * @param OMB_Profile $profile The profile that has changed
+ *
+ * @access public
+ **/
+ public function updateProfile($profile) {
+ $params = $profile->asParameters('omb_listenee', true);
+ $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
+ }
+
+ /**
+ * Inform the service about a new notice
+ *
+ * Sends a notice to the service.
+ *
+ * @param OMB_Notice $notice The notice
+ *
+ * @access public
+ **/
+ public function postNotice($notice) {
+ $params = $notice->asParameters();
+ $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
+ $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']);
+ }
+
+ /**
+ * Set the token member variable
+ *
+ * Initializes the token based on given token and secret token.
+ *
+ * @param string $token The token
+ * @param string $secret The secret token
+ *
+ * @access public
+ **/
+ public function setToken($token, $secret) {
+ $this->token = new OAuthToken($token, $secret);
+ }
+
+ /**
+ * Prepare an OAuthRequest object
+ *
+ * Creates an OAuthRequest object mapping the request specified by the
+ * parameters.
+ *
+ * @param string $action_uri The URI specifying the target service
+ * @param array $params Additional parameters for the service call
+ * @param string $method The HTTP method used to call the service
+ * ('POST' or 'GET', usually)
+ *
+ * @access protected
+ *
+ * @return OAuthRequest the prepared request
+ **/
+ protected function prepareAction($action_uri, $params, $method) {
+ $url = $this->services[$action_uri];
+
+ $url_params = array();
+ parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
+
+ /* Add OMB version. */
+ $url_params['omb_version'] = OMB_VERSION;
+
+ /* Add user-defined parameters. */
+ $url_params = array_merge($url_params, $params);
+
+ $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
+ $this->token, $method, $url, $url_params);
+
+ /* Sign the request. */
+ $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
+ $this->oauth_consumer, $this->token);
+
+ return $req;
+ }
+
+ /**
+ * Perform a service call
+ *
+ * Creates an OAuthRequest object and execute the mapped call as POST request.
+ *
+ * @param string $action_uri The URI specifying the target service
+ * @param array $params Additional parameters for the service call
+ *
+ * @access protected
+ *
+ * @return Auth_Yadis_HTTPResponse The POST request response
+ **/
+ protected function performAction($action_uri, $params) {
+ $req = $this->prepareAction($action_uri, $params, 'POST');
+
+ /* Return result page. */
+ return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
+ }
+
+ /**
+ * Perform an OMB action
+ *
+ * Executes an OMB action – to date, it’s one of updateProfile or postNotice.
+ *
+ * @param string $action_uri The URI specifying the target service
+ * @param array $params Additional parameters for the service call
+ * @param string $listenee_uri The URI identifying the local user for whom
+ * the action is performed
+ *
+ * @access protected
+ **/
+ protected function performOMBAction($action_uri, $params, $listenee_uri) {
+ $result = $this->performAction($action_uri, $params);
+ if ($result->status == 403) {
+ /* The remote user unsubscribed us. */
+ $this->datastore->deleteSubscription($this->listener_uri, $listenee_uri);
+ } else if ($result->status != 200 ||
+ strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
+ /* The server signaled an error or sent an incorrect response. */
+ throw OMB_RemoteServiceException::fromYadis($action_uri, $result);
+ }
+ }
+}
diff --git a/extlib/libomb/service_provider.php b/extlib/libomb/service_provider.php
new file mode 100755
index 000000000..753152713
--- /dev/null
+++ b/extlib/libomb/service_provider.php
@@ -0,0 +1,425 @@
+<?php
+
+require_once 'constants.php';
+require_once 'remoteserviceexception.php';
+require_once 'helper.php';
+
+/**
+ * OMB service realization
+ *
+ * This class realizes a complete, simple OMB service.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Service_Provider {
+ protected $user; /* An OMB_Profile representing the user */
+ protected $datastore; /* AN OMB_Datastore */
+
+ protected $remote_user; /* An OMB_Profile representing the remote user during
+ the authorization process */
+
+ protected $oauth_server; /* An OAuthServer; should only be accessed via
+ getOAuthServer. */
+
+ /**
+ * Initialize an OMB_Service_Provider object
+ *
+ * Constructs an OMB_Service_Provider instance that provides OMB services
+ * referring to a particular user.
+ *
+ * @param OMB_Profile $user An OMB_Profile; mandatory for XRDS
+ * output, user auth handling and OMB
+ * action performing
+ * @param OMB_Datastore $datastore An OMB_Datastore; mandatory for
+ * everything but XRDS output
+ * @param OAuthServer $oauth_server An OAuthServer; used for token writing
+ * and OMB action handling; will use
+ * default value if not set
+ *
+ * @access public
+ **/
+ public function __construct ($user = null, $datastore = null, $oauth_server = null) {
+ $this->user = $user;
+ $this->datastore = $datastore;
+ $this->oauth_server = $oauth_server;
+ }
+
+ public function getRemoteUser() {
+ return $this->remote_user;
+ }
+
+ /**
+ * Write a XRDS document
+ *
+ * Writes a XRDS document specifying the OMB service. Optionally uses a
+ * given object of a class implementing OMB_XRDS_Writer for output. Else
+ * OMB_Plain_XRDS_Writer is used.
+ *
+ * @param OMB_XRDS_Mapper $xrds_mapper An object mapping actions to URLs
+ * @param OMB_XRDS_Writer $xrds_writer Optional; The OMB_XRDS_Writer used to
+ * write the XRDS document
+ *
+ * @access public
+ *
+ * @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer
+ * returns nothing.
+ **/
+ public function writeXRDS($xrds_mapper, $xrds_writer = null) {
+ if ($xrds_writer == null) {
+ require_once 'plain_xrds_writer.php';
+ $xrds_writer = new OMB_Plain_XRDS_Writer();
+ }
+ return $xrds_writer->writeXRDS($this->user, $xrds_mapper);
+ }
+
+ /**
+ * Echo a request token
+ *
+ * Outputs an unauthorized request token for the query found in $_GET or
+ * $_POST.
+ *
+ * @access public
+ **/
+ public function writeRequestToken() {
+ OMB_Helper::removeMagicQuotesFromRequest();
+ echo $this->getOAuthServer()->fetch_request_token(OAuthRequest::from_request());
+ }
+
+ /**
+ * Handle an user authorization request.
+ *
+ * Parses an authorization request. This includes OAuth and OMB verification.
+ * Throws exceptions on failures. Returns an OMB_Profile object representing
+ * the remote user.
+ *
+ * The OMB_Profile passed to the constructor of OMB_Service_Provider should
+ * not represent the user specified in the authorization request, but the one
+ * currently logged in to the service. This condition being satisfied,
+ * handleUserAuth will check whether the listener specified in the request is
+ * identical to the logged in user.
+ *
+ * @access public
+ *
+ * @return OMB_Profile The profile of the soon-to-be subscribed, i. e. remote
+ * user
+ **/
+ public function handleUserAuth() {
+ OMB_Helper::removeMagicQuotesFromRequest();
+
+ /* Verify the request token. */
+
+ $this->token = $this->datastore->lookup_token(null, "request", $_GET['oauth_token']);
+ if (is_null($this->token)) {
+ throw new OAuthException('The given request token has not been issued ' .
+ 'by this service.');
+ }
+
+ /* Verify the OMB part. */
+
+ if ($_GET['omb_version'] !== OMB_VERSION) {
+ throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+ 'Wrong OMB version ' . $_GET['omb_version']);
+ }
+
+ if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
+ throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+ 'Wrong OMB listener ' . $_GET['omb_listener']);
+ }
+
+ foreach (array('omb_listenee', 'omb_listenee_profile',
+ 'omb_listenee_nickname', 'omb_listenee_license') as $param) {
+ if (!isset($_GET[$param]) || is_null($_GET[$param])) {
+ throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+ "Required parameter '$param' not found");
+ }
+ }
+
+ /* Store given callback for later use. */
+ if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') {
+ $this->callback = $_GET['oauth_callback'];
+ if (!OMB_Helper::validateURL($this->callback)) {
+ throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+ 'Invalid callback URL specified');
+ }
+ }
+ $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
+
+ return $this->remote_user;
+ }
+
+ /**
+ * Continue the OAuth dance after user authorization
+ *
+ * Performs the appropriate actions after user answered the authorization
+ * request.
+ *
+ * @param bool $accepted Whether the user granted authorization
+ *
+ * @access public
+ *
+ * @return array A two-component array with the values:
+ * - callback The callback URL or null if none given
+ * - token The authorized request token or null if not
+ * authorized.
+ **/
+ public function continueUserAuth($accepted) {
+ $callback = $this->callback;
+ if (!$accepted) {
+ $this->datastore->revoke_token($this->token->key);
+ $this->token = null;
+ /* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way
+ laconica works. Moreover I don’t know the right way either. */
+
+ } else {
+ $this->datastore->authorize_token($this->token->key);
+ $this->datastore->saveProfile($this->remote_user);
+ $this->datastore->saveSubscription($this->user->getIdentifierURI(),
+ $this->remote_user->getIdentifierURI(), $this->token);
+
+ if (!is_null($this->callback)) {
+ /* Callback wants to get some informations as well. */
+ $params = $this->user->asParameters('omb_listener', false);
+
+ $params['oauth_token'] = $this->token->key;
+ $params['omb_version'] = OMB_VERSION;
+
+ $callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
+ foreach ($params as $k => $v) {
+ $callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
+ OAuthUtil::urlencode_rfc3986($v) . '&';
+ }
+ }
+ }
+ return array($callback, $this->token);
+ }
+
+ /**
+ * Echo an access token
+ *
+ * Outputs an access token for the query found in $_POST. OMB 0.1 specifies
+ * that the access token request has to be a POST even if OAuth allows GET as
+ * well.
+ *
+ * @access public
+ **/
+ public function writeAccessToken() {
+ OMB_Helper::removeMagicQuotesFromRequest();
+ echo $this->getOAuthServer()->fetch_access_token(
+ OAuthRequest::from_request('POST'));
+ }
+
+ /**
+ * Handle an updateprofile request
+ *
+ * Handles an updateprofile request posted to this service. Updates the
+ * profile through the OMB_Datastore.
+ *
+ * @access public
+ *
+ * @return OMB_Profile The updated profile
+ **/
+ public function handleUpdateProfile() {
+ list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE);
+ $profile->updateFromParameters($req->get_parameters(), 'omb_listenee');
+ $this->datastore->saveProfile($profile);
+ $this->finishOMBRequest();
+ return $profile;
+ }
+
+ /**
+ * Handle a postnotice request
+ *
+ * Handles a postnotice request posted to this service. Saves the notice
+ * through the OMB_Datastore.
+ *
+ * @access public
+ *
+ * @return OMB_Notice The received notice
+ **/
+ public function handlePostNotice() {
+ list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
+ require_once 'notice.php';
+ $notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
+ $this->datastore->saveNotice($notice);
+ $this->finishOMBRequest();
+ return $notice;
+ }
+
+ /**
+ * Handle an OMB request
+ *
+ * Performs common OMB request handling.
+ *
+ * @param string $uri The URI defining the OMB endpoint being served
+ *
+ * @access protected
+ *
+ * @return array(OAuthRequest, OMB_Profile)
+ **/
+ protected function handleOMBRequest($uri) {
+
+ OMB_Helper::removeMagicQuotesFromRequest();
+ $req = OAuthRequest::from_request('POST');
+ $listenee = $req->get_parameter('omb_listenee');
+
+ try {
+ list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
+ } catch (OAuthException $e) {
+ header('HTTP/1.1 403 Forbidden');
+ throw OMB_RemoteServiceException::forRequest($uri,
+ 'Revoked accesstoken for ' . $listenee);
+ }
+
+ $version = $req->get_parameter('omb_version');
+ if ($version !== OMB_VERSION) {
+ header('HTTP/1.1 400 Bad Request');
+ throw OMB_RemoteServiceException::forRequest($uri,
+ 'Wrong OMB version ' . $version);
+ }
+
+ $profile = $this->datastore->getProfile($listenee);
+ if (is_null($profile)) {
+ header('HTTP/1.1 400 Bad Request');
+ throw OMB_RemoteServiceException::forRequest($uri,
+ 'Unknown remote profile ' . $listenee);
+ }
+
+ $subscribers = $this->datastore->getSubscriptions($listenee);
+ if (count($subscribers) === 0) {
+ header('HTTP/1.1 403 Forbidden');
+ throw OMB_RemoteServiceException::forRequest($uri,
+ 'No subscriber for ' . $listenee);
+ }
+
+ return array($req, $profile);
+ }
+
+ /**
+ * Finishes an OMB request handling
+ *
+ * Performs common OMB request handling finishing.
+ *
+ * @access protected
+ **/
+ protected function finishOMBRequest() {
+ header('HTTP/1.1 200 OK');
+ header('Content-type: text/plain');
+ /* There should be no clutter but the version. */
+ echo "omb_version=" . OMB_VERSION;
+ }
+
+ /**
+ * Return an OAuthServer
+ *
+ * Checks whether the OAuthServer is null. If so, initializes it with a
+ * default value. Returns the OAuth server.
+ *
+ * @access protected
+ **/
+ protected function getOAuthServer() {
+ if (is_null($this->oauth_server)) {
+ $this->oauth_server = new OAuthServer($this->datastore);
+ $this->oauth_server->add_signature_method(
+ new OAuthSignatureMethod_HMAC_SHA1());
+ }
+ return $this->oauth_server;
+ }
+
+ /**
+ * Publish a notice
+ *
+ * Posts an OMB notice. This includes storing the notice and posting it to
+ * subscribed users.
+ *
+ * @param OMB_Notice $notice The new notice
+ *
+ * @access public
+ *
+ * @return array An array mapping subscriber URIs to the exception posting to
+ * them has raised; Empty array if no exception occured
+ **/
+ public function postNotice($notice) {
+ $uri = $this->user->getIdentifierURI();
+
+ /* $notice is passed by reference and may change. */
+ $this->datastore->saveNotice($notice);
+ $subscribers = $this->datastore->getSubscriptions($uri);
+
+ /* No one to post to. */
+ if (is_null($subscribers)) {
+ return array();
+ }
+
+ require_once 'service_consumer.php';
+
+ $err = array();
+ foreach($subscribers as $subscriber) {
+ try {
+ $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
+ $service->setToken($subscriber['token'], $subscriber['secret']);
+ $service->postNotice($notice);
+ } catch (Exception $e) {
+ $err[$subscriber['uri']] = $e;
+ continue;
+ }
+ }
+ return $err;
+ }
+
+ /**
+ * Publish a profile update
+ *
+ * Posts the current profile as an OMB profile update. This includes updating
+ * the stored profile and posting it to subscribed users.
+ *
+ * @access public
+ *
+ * @return array An array mapping subscriber URIs to the exception posting to
+ * them has raised; Empty array if no exception occured
+ **/
+ public function updateProfile() {
+ $uri = $this->user->getIdentifierURI();
+
+ $this->datastore->saveProfile($this->user);
+ $subscribers = $this->datastore->getSubscriptions($uri);
+
+ /* No one to post to. */
+ if (is_null($subscribers)) {
+ return array();
+ }
+
+ require_once 'service_consumer.php';
+
+ $err = array();
+ foreach($subscribers as $subscriber) {
+ try {
+ $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
+ $service->setToken($subscriber['token'], $subscriber['secret']);
+ $service->updateProfile($this->user);
+ } catch (Exception $e) {
+ $err[$subscriber['uri']] = $e;
+ continue;
+ }
+ }
+ return $err;
+ }
+}
diff --git a/extlib/libomb/unsupportedserviceexception.php b/extlib/libomb/unsupportedserviceexception.php
new file mode 100755
index 000000000..4dab63ebe
--- /dev/null
+++ b/extlib/libomb/unsupportedserviceexception.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Exception stating that a requested service is not available
+ *
+ * This exception is raised when OMB_Service is asked to call a service the remote
+ * server does not provide.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_UnsupportedServiceException extends Exception {
+
+}
+?>
diff --git a/extlib/libomb/xrds_mapper.php b/extlib/libomb/xrds_mapper.php
new file mode 100755
index 000000000..7552154e5
--- /dev/null
+++ b/extlib/libomb/xrds_mapper.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Map XRDS actions to URLs
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+interface OMB_XRDS_Mapper {
+ public function getURL($action);
+}
+?>
diff --git a/extlib/libomb/xrds_writer.php b/extlib/libomb/xrds_writer.php
new file mode 100755
index 000000000..31b451b9c
--- /dev/null
+++ b/extlib/libomb/xrds_writer.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Write OMB-specific XRDS
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package OMB
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+interface OMB_XRDS_Writer {
+ public function writeXRDS($user, $mapper);
+}
+?>
diff --git a/extlib/php-gettext/gettext.inc b/extlib/php-gettext/gettext.inc
index fcaafe7c9..7c95e40ec 100644
--- a/extlib/php-gettext/gettext.inc
+++ b/extlib/php-gettext/gettext.inc
@@ -129,7 +129,7 @@ function _setlocale($category, $locale) {
$ret = 0;
if (function_exists('setlocale')) // I don't know if this ever happens ;)
$ret = setlocale($category, $locale);
- if (($ret and $locale == '') or ($ret == $locale)) {
+ if ($ret and ($locale == '' or $ret == $locale)) {
$EMULATEGETTEXT = 0;
$CURRENTLOCALE = $ret;
} else {