<?php
/**
 * Twitter class
 *
 * This source file can be used to communicate with Twitter (http://twitter.com)
 *
 * The class is documented in the file itself. If you find any bugs help me out and report them. Reporting can be done by sending an email to php-twitter-bugs[at]verkoyen[dot]eu.
 * If you report a bug, make sure you give me enough information (include your code).
 *
 * Changelog since 1.0.0
 * - dont send postfields as an array, pass them as an urldecoded string (otherwise @ won't work
 *
 * Changelog since 1.0.1
 * - fixed a bug in verifyCredentials, it return a boolean instead of throwing an exception when the credentials are invalid (thx @Rahul)
 *
 * Changelog since 1.0.2
 * - sinceId is from now on treated as a string instead of int. (thx @Paul Matthews)
 *
 * Changelog since 1.0.3
 * - rewrote some comments
 * - fixed some PHPDoc
 * - it seems Twitter removed the $since-parameter, so I removed it from getFriendsTimeline, getUserTimeline, getDirectMessages, getSentDirectMessages, ...
 * - implemented maxId into getFriendsTimeline
 * - renamed getReplies to getMentions to reflect the Twitter API
 * - added $count for getDirectMessages, getSentDirectMessages, ...
 * - added getFriendship which shows more details about a friendship
 * - added getFriendIds and getFollowerIds which return only the ids instead of a user-array
 * - added existsBlock which test if a block exists
 * - added getBlocked, which returns an array of blocked user-arrays
 * - added getBlockedIds, which returns an array of blocked ids
 *
 * License
 * Copyright (c) 2008, Tijs Verkoyen. 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.
 * 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
 *
 * 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.
 *
 * @author		Tijs Verkoyen <php-twitter@verkoyen.eu>
 * @version		1.0.4
 *
 * @copyright	Copyright (c) 2008, Tijs Verkoyen. All rights reserved.
 * @license		BSD License
 */
class Twitter
{
	// internal constant to enable/disable debugging
	const DEBUG = false;

	// url for the twitter-api
	const TWITTER_API_URL = 'http://twitter.com';

	// port for the twitter-api
	const TWITTER_API_PORT = 80;

	// current version
	const VERSION = '1.0.4';


	/**
	 * The password for an authenticating user
	 *
	 * @var	string
	 */
	private $password;


	/**
	 * The timeout
	 *
	 * @var	int
	 */
	private $timeOut = 60;


	/**
	 * The user agent
	 *
	 * @var	string
	 */
	private $userAgent;


	/**
	 * The username for an authenticating user
	 *
	 * @var	string
	 */
	private $username;


// class methods
	/**
	 * Default constructor
	 *
	 * @return	void
	 * @param	string[optional] $username
	 * @param	string[optional] $password
	 */
	public function __construct($username = null, $password = null)
	{
		if($username !== null) $this->setUsername($username);
		if($password !== null) $this->setPassword($password);
	}


	/**
	 * Make the call
	 *
	 * @return	string
	 * @param	string $url
	 * @param	array[optiona] $aParameters
	 * @param	bool[optional] $authenticate
	 * @param	bool[optional] $usePost
	 */
	private function doCall($url, $aParameters = array(), $authenticate = false, $usePost = true)
	{
		// redefine
		$url = (string) $url;
		$aParameters = (array) $aParameters;
		$authenticate = (bool) $authenticate;
		$usePost = (bool) $usePost;

		// build url
		$url = self::TWITTER_API_URL .'/'. $url;

		// validate needed authentication
		if($authenticate && ($this->getUsername() == '' || $this->getPassword() == '')) throw new TwitterException('No username or password was set.');

		// rebuild url if we don't use post
		if(!empty($aParameters) && !$usePost)
		{
			// init var
			$queryString = '';

			// loop parameters and add them to the queryString
			foreach($aParameters as $key => $value) $queryString .= '&'. $key .'='. urlencode(utf8_encode($value));

			// cleanup querystring
			$queryString = trim($queryString, '&');

			// append to url
			$url .= '?'. $queryString;
		}

		// set options
		$options[CURLOPT_URL] = $url;
		$options[CURLOPT_PORT] = self::TWITTER_API_PORT;
		$options[CURLOPT_USERAGENT] = $this->getUserAgent();
		$options[CURLOPT_FOLLOWLOCATION] = true;
		$options[CURLOPT_RETURNTRANSFER] = true;
		$options[CURLOPT_TIMEOUT] = (int) $this->getTimeOut();

		// should we authenticate?
		if($authenticate)
		{
			$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
			$options[CURLOPT_USERPWD] = $this->getUsername() .':'. $this->getPassword();
		}

		// are there any parameters?
		if(!empty($aParameters) && $usePost)
		{
			$var = '';

			// rebuild parameters
			foreach($aParameters as $key => $value) $var .= '&'. $key .'='. urlencode($value);

			// set extra options
			$options[CURLOPT_POST] = true;
			$options[CURLOPT_POSTFIELDS] = trim($var, '&');

			// Probaly Twitter's webserver doesn't support the Expect: 100-continue header. So we reset it.
			$options[CURLOPT_HTTPHEADER] = array('Expect:');
		}

		// init
		$curl = curl_init();

		// set options
		curl_setopt_array($curl, $options);

		// execute
		$response = curl_exec($curl);
		$headers = curl_getinfo($curl);

		// fetch errors
		$errorNumber = curl_errno($curl);
		$errorMessage = curl_error($curl);

		// close
		curl_close($curl);

		// validate body
		$xml = @simplexml_load_string($response);
		if($xml !== false && isset($xml->error)) throw new TwitterException((string) $xml->error);

		// invalid headers
		if(!in_array($headers['http_code'], array(0, 200)))
		{
			// should we provide debug information
			if(self::DEBUG)
			{
				// make it output proper
				echo '<pre>';

				// dump the header-information
				var_dump($headers);

				// dump the raw response
				var_dump($response);

				// end proper format
				echo '</pre>';

				// stop the script
				exit;
			}

			// throw error
			throw new TwitterException(null, (int) $headers['http_code']);
		}

		// error?
		if($errorNumber != '') throw new TwitterException($errorMessage, $errorNumber);

		// return
		return $response;
	}


	/**
	 * Get the password
	 *
	 * @return	string
	 */
	private function getPassword()
	{
		return (string) $this->password;
	}


	/**
	 * Get the timeout
	 *
	 * @return	int
	 */
	public function getTimeOut()
	{
		return (int) $this->timeOut;
	}


	/**
	 * Get the useragent
	 *
	 * @return	string
	 */
	public function getUserAgent()
	{
		return (string) 'PHP Twitter/'. self::VERSION .' '. $this->userAgent;
	}


	/**
	 * Get the username
	 *
	 * @return	string
	 */
	private function getUsername()
	{
		return (string) $this->username;
	}


	/**
	 * Converts a piece of XML into a message-array
	 *
	 * @return	array
	 * @param	SimpleXMLElement $xml
	 */
	private function messageXMLToArray($xml)
	{
		// validate xml
		if(!isset($xml->id, $xml->text, $xml->created_at, $xml->sender, $xml->recipient)) throw new TwitterException('Invalid xml for message.');

		// convert into array
		$aMessage['id'] = (string) $xml->id;
		$aMessage['created_at'] = (int) strtotime($xml->created_at);
		$aMessage['text'] = (string) utf8_decode($xml->text);
		$aMessage['sender'] = $this->userXMLToArray($xml->sender);
		$aMessage['recipient'] = $this->userXMLToArray($xml->recipient);

		// return
		return $aMessage;
	}


	/**
	 * Set password
	 *
	 * @return	void
	 * @param	string $password
	 */
	public function setPassword($password)
	{
		$this->password = (string) $password;
	}


	/**
	 * Set the timeout
	 *
	 * @return	void
	 * @param	int $seconds
	 */
	public function setTimeOut($seconds)
	{
		$this->timeOut = (int) $seconds;
	}


	/**
	 * Set the user-agent for you application
	 * It will be appended to ours
	 *
	 * @return	void
	 * @param	string $userAgent
	 */
	public function setUserAgent($userAgent)
	{
		$this->userAgent = (string) $userAgent;
	}


	/**
	 * Set username
	 *
	 * @return	void
	 * @param	string $username
	 */
	public function setUsername($username)
	{
		$this->username = (string) $username;
	}


	/**
	 * Converts a piece of XML into a status-array
	 *
	 * @return	array
	 * @param	SimpleXMLElement $xml
	 */
	private function statusXMLToArray($xml)
	{
		// validate xml
		if(!isset($xml->id, $xml->text, $xml->created_at, $xml->source, $xml->truncated, $xml->in_reply_to_status_id, $xml->in_reply_to_user_id, $xml->favorited, $xml->user)) throw new TwitterException('Invalid xml for message.');

		// convert into array
		$aStatus['id'] = (string) $xml->id;
		$aStatus['created_at'] = (int) strtotime($xml->created_at);
		$aStatus['text'] = utf8_decode((string) $xml->text);
		$aStatus['source'] = (isset($xml->source)) ? (string) $xml->source : '';
		$aStatus['user'] = $this->userXMLToArray($xml->user);
		$aStatus['truncated'] = (isset($xml->truncated) && $xml->truncated == 'true');
		$aStatus['favorited'] = (isset($xml->favorited) && $xml->favorited == 'true');
		$aStatus['in_reply_to_status_id'] = (string) $xml->in_reply_to_status_id;
		$aStatus['in_reply_to_user_id'] = (string) $xml->in_reply_to_user_id;

		// return
		return $aStatus;
	}


	/**
	 * Converts a piece of XML into an user-array
	 *
	 * @return	array
	 * @param	SimpleXMLElement $xml
	 */
	private function userXMLToArray($xml, $extended = false)
	{
		// validate xml
		if(!isset($xml->id, $xml->name, $xml->screen_name, $xml->description, $xml->location, $xml->profile_image_url, $xml->url, $xml->protected, $xml->followers_count)) throw new TwitterException('Invalid xml for message.');


		// convert into array
		$aUser['id'] = (string) $xml->id;
		$aUser['name'] = utf8_decode((string) $xml->name);
		$aUser['screen_name'] = utf8_decode((string) $xml->screen_name);
		$aUser['description'] = utf8_decode((string) $xml->description);
		$aUser['location'] = utf8_decode((string) $xml->location);
		$aUser['url'] = (string) $xml->url;
		$aUser['protected'] = (isset($xml->protected) && $xml->protected == 'true');
		$aUser['followers_count'] = (int) $xml->followers_count;
		$aUser['profile_image_url'] = (string) $xml->profile_image_url;

		// extended info?
		if($extended)
		{
			if(isset($xml->profile_background_color)) $aUser['profile_background_color'] = utf8_decode((string) $xml->profile_background_color);
			if(isset($xml->profile_text_color)) $aUser['profile_text_color'] = utf8_decode((string) $xml->profile_text_color);
			if(isset($xml->profile_link_color)) $aUser['profile_link_color'] = utf8_decode((string) $xml->profile_link_color);
			if(isset($xml->profile_sidebar_fill_color)) $aUser['profile_sidebar_fill_color'] = utf8_decode((string) $xml->profile_sidebar_fill_color);
			if(isset($xml->profile_sidebar_border_color)) $aUser['profile_sidebar_border_color'] = utf8_decode((string) $xml->profile_sidebar_border_color);
			if(isset($xml->profile_background_image_url)) $aUser['profile_background_image_url'] = utf8_decode((string) $xml->profile_background_image_url);
			if(isset($xml->profile_background_tile)) $aUser['profile_background_tile'] = (isset($xml->profile_background_tile) && $xml->profile_background_tile == 'true');
			if(isset($xml->created_at)) $aUser['created_at'] = (int) strtotime((string) $xml->created_at);
			if(isset($xml->following)) $aUser['following'] = (isset($xml->following) && $xml->following == 'true');
			if(isset($xml->notifications)) $aUser['notifications'] = (isset($xml->notifications) && $xml->notifications == 'true');
			if(isset($xml->statuses_count)) $aUser['statuses_count'] = (int) $xml->statuses_count;
			if(isset($xml->friends_count)) $aUser['friends_count'] =  (int) $xml->friends_count;
			if(isset($xml->favourites_count)) $aUser['favourites_count'] = (int) $xml->favourites_count;
			if(isset($xml->time_zone)) $aUser['time_zone'] = utf8_decode((string) $xml->time_zone);
			if(isset($xml->utc_offset)) $aUser['utc_offset'] = (int) $xml->utc_offset;
		}

		// return
		return (array) $aUser;
	}


// timeline methods
	/**
	 * Returns the 20 most recent statuses from non-protected users who have set a custom user icon.
	 * Note that the public timeline is cached for 60 seconds so requesting it more often than that is a waste of resources.
	 *
	 * @return	array
	 */
	public function getPublicTimeline()
	{
		// do the call
		$response = $this->doCall('statuses/public_timeline.xml');

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aStatuses = array();

		// loop statuses
		foreach ($xml->status as $status) $aStatuses[] = $this->statusXMLToArray($status);

		// return
		return (array) $aStatuses;
	}


	/**
	 * Returns the 20 most recent statuses posted by the authenticating user and that user's friends.
	 * This is the equivalent of /home on the Web.
	 *
	 * @return	array
	 * @param	string[optional] $sinceId	Returns only statuses with an id greater than (that is, more recent than) the specified $sinceId.
	 * @param	string[optional] $maxId	Returns only statuses with an ID less than (that is, older than) or equal to the specified $maxId.
	 * @param	int[optional] $count	Specifies the number of statuses to retrieve. May not be greater than 200.
	 * @param	int[optional] $page
	 */
	public function getFriendsTimeline($sinceId = null, $maxId = null, $count = null, $page = null)
	{
		// validate parameters
		if($sinceId !== null && (string) $sinceId == '') throw new TwitterException('Invalid value for sinceId.');
		if($maxId !== null && (string) $maxId == '') throw new TwitterException('Invalid value for maxId.');
		if($count !== null && (int) $count > 200) throw new TwitterException('Count can\'t be larger then 200.');

		// build url
		$aParameters = array();
		if($sinceId !== null) $aParameters['since_id'] = (string) $sinceId;
		if($maxId !== null) $aParameters['max_id'] = (string) $maxId;
		if($count !== null) $aParameters['count'] = (int) $count;
		if($page !== null) $aParameters['page'] = (int) $page;

		// do the call
		$response = $this->doCall('statuses/friends_timeline.xml', $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aStatuses = array();

		// loop statuses
		foreach ($xml->status as $status) $aStatuses[] = $this->statusXMLToArray($status);

		// return
		return (array) $aStatuses;
	}


	/**
	 * Returns the 20 most recent statuses posted from the authenticating user. It's also possible to request another user's timeline via the id parameter below.
	 * This is the equivalent of the Web /archive page for your own user, or the profile page for a third party.
	 *
	 * @return	array
	 * @param	string[optional] $id	Specifies the id or screen name of the user for whom to return the friends_timeline.
	 * @param	string[optional] $sinceId	Returns only statuses with an id greater than (that is, more recent than) the specified $sinceId.
	 * @param	string[optional] $maxId	Returns only statuses with an ID less than (that is, older than) or equal to the specified $maxId.
	 * @param	int[optional] $count	Specifies the number of statuses to retrieve. May not be greater than 200.
	 * @param	int[optional] $page	Specifies the page or results to retrieve.
	 */
	public function getUserTimeline($id = null, $sinceId = null, $maxId = null, $count = null, $page = null)
	{
		// validate parameters
		if($sinceId !== null && (string) $sinceId == '') throw new TwitterException('Invalid value for sinceId.');
		if($maxId !== null && (string) $maxId == '') throw new TwitterException('Invalid value for maxId.');
		if($count !== null && (int) $count > 200) throw new TwitterException('Count can\'t be larger then 200.');

		// build parameters
		$aParameters = array();
		if($sinceId !== null) $aParameters['since_id'] = (string) $sinceId;
		if($maxId !== null) $aParameters['max_id'] = (string) $maxId;
		if($count !== null) $aParameters['count'] = (int) $count;
		if($page !== null) $aParameters['page'] = (int) $page;

		// build url
		$url = 'statuses/user_timeline.xml';
		if($id !== null) $url = 'statuses/user_timeline/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aStatuses = array();

		// loop statuses
		foreach ($xml->status as $status) $aStatuses[] = $this->statusXMLToArray($status);

		// return
		return (array) $aStatuses;
	}


	/**
	 * Returns the 20 most recent mentions (status containing @username) for the authenticating user.
	 *
	 * @return	array
	 * @param	string[optional] $sinceId	Returns only statuses with an id greater than (that is, more recent than) the specified $sinceId.
	 * @param	string[optional] $maxId	Returns only statuses with an ID less than (that is, older than) or equal to the specified $maxId.
	 * @param	int[optional] $count	Specifies the number of statuses to retrieve. May not be greater than 200.
	 * @param	int[optional] $page	Specifies the page or results to retrieve.
	 */
	public function getMentionsReplies($sinceId = null, $maxId = null, $count = null, $page = null)
	{
		// validate parameters
		if($sinceId !== null && (string) $sinceId == '') throw new TwitterException('Invalid value for sinceId.');
		if($maxId !== null && (string) $maxId == '') throw new TwitterException('Invalid value for maxId.');
		if($count !== null && (int) $count > 200) throw new TwitterException('Count can\'t be larger then 200.');

		// build parameters
		$aParameters = array();
		if($sinceId !== null) $aParameters['since_id'] = (string) $sinceId;
		if($maxId !== null) $aParameters['max_id'] = (string) $maxId;
		if($count !== null) $aParameters['count'] = (int) $count;
		if($page !== null) $aParameters['page'] = (int) $page;

		// do the call
		$response = $this->doCall('statuses/mentions.xml', $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aStatuses = array();

		// loop statuses
		foreach ($xml->status as $status) $aStatuses[] = $this->statusXMLToArray($status);

		// return
		return (array) $aStatuses;
	}


// status methods
	/**
	 * Returns a single status, specified by the id parameter below.
	 *
	 * @return	array
	 * @param	int $id	The numerical id of the status you're trying to retrieve.
	 */
	public function getStatus($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'statuses/show/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->statusXMLToArray($xml);
	}


	/**
	 * Updates the authenticating user's status.
	 * A status update with text identical to the authenticating user's current status will be ignored.
	 *
	 * @return	array
	 * @param	string $status	The text of your status update. Should not be more than 140 characters.
	 * @param	int[optional] $inReplyToId	The id of an existing status that the status to be posted is in reply to.
	 */
	public function updateStatus($status, $inReplyToId = null)
	{
		// redefine
		$status = (string) $status;

		// validate parameters
		if(strlen($status) > 140) throw new TwitterException('Maximum 140 characters allowed for status.');

		// build parameters
		$aParameters = array();
		$aParameters['status'] = $status;
		if($inReplyToId !== null) $aParameters['in_reply_to_status_id'] = (int) $inReplyToId;

		// do the call
		$response = $this->doCall('statuses/update.xml', $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->statusXMLToArray($xml);
	}


	/**
	 * Destroys the status specified by the required $id parameter.
	 * The authenticating user must be the author of the specified status.
	 *
	 * @return	array
	 * @param	int[optional] $id
	 */
	public function deleteStatus($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'statuses/destroy/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->statusXMLToArray($xml);
	}


// user methods
	/**
	 * Returns extended information of a given user, specified by id or screen name.
	 * This information includes design settings, so third party developers can theme their widgets according to a given user's preferences.
	 * You must be properly authenticated to request the page of a protected user.
	 *
	 * @return	array
	 * @param	string $id	The id or screen name of a user.
	 */
	public function getUser($id)
	{
		// build parameters
		$aParameters = array();

		// build url
		$url = 'users/show/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml, true);
	}


	/**
	 * Returns up to 100 of the authenticating user's friends who have most recently updated.
	 * It's also possible to request another user's recent friends list via the $id parameter.
	 *
	 * @return	array
	 * @param	string[optional] $id	The id or screen name of the user for whom to request a list of friends.
	 * @param	int[optional] $page	Specifies the page of friends to receive.
	 */
	public function getFriends($id = null, $page = null)
	{
		// build parameters
		$aParameters = array();
		if($page !== null) $aParameters['page'] = (int) $page;

		// build url
		$url = 'statuses/friends.xml';
		if($id !== null) $url = 'statuses/friends/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aUsers = array();

		// loop statuses
		foreach ($xml->user as $user) $aUsers[] = $this->userXMLToArray($user);

		// return
		return (array) $aUsers;
	}


	/**
	 * Returns the authenticating user's followers.
	 *
	 * @return	array
	 * @param	string[optional] $id	 The id or screen name of the user for whom to request a list of followers.
	 * @param	int[optional] $page
	 */
	public function getFollowers($id = null, $page = null)
	{
		// build parameters
		$aParameters = array();
		if($page !== null) $aParameters['page'] = (int) $page;

		// build url
		$url = 'statuses/followers.xml';
		if($id !== null) $url = 'statuses/followers/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aUsers = array();

		// loop statuses
		foreach ($xml->user as $user) $aUsers[] = $this->userXMLToArray($user);

		// return
		return (array) $aUsers;
	}



// direct message methods
	/**
	 * Returns a list of the 20 most recent direct messages sent to the authenticating user.
	 *
	 * @return	array
	 * @param	string[optional] $sinceId	Returns only direct messages with an id greater than (that is, more recent than) the specified $sinceId.
	 * @param	string[optional] $maxId	Returns only statuses with an ID less than (that is, older than) or equal to the specified $maxId.
	 * @param	int[optional] $count	Specifies the number of statuses to retrieve. May not be greater than 200.
	 * @param	int[optional] $page
	 */
	public function getDirectMessages($sinceId = null, $maxId = null, $count = null, $page = null)
	{
		// validate parameters
		if($sinceId !== null && (string) $sinceId == '') throw new TwitterException('Invalid value for sinceId.');
		if($maxId !== null && (string) $maxId == '') throw new TwitterException('Invalid value for maxId.');
		if($count !== null && (int) $count > 200) throw new TwitterException('Count can\'t be larger then 200.');

		// build url
		$aParameters = array();
		if($sinceId !== null) $aParameters['since_id'] = (string) $sinceId;
		if($maxId !== null) $aParameters['max_id'] = (string) $maxId;
		if($count !== null) $aParameters['count'] = (int) $count;
		if($page !== null) $aParameters['page'] = (int) $page;

		// do the call
		$response = $this->doCall('direct_messages.xml', $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aDirectMessages = array();

		// loop statuses
		foreach ($xml->direct_message as $message) $aDirectMessages[] = $this->messageXMLToArray($message);

		// return
		return (array) $aDirectMessages;
	}


	/**
	 * Returns a list of the 20 most recent direct messages sent by the authenticating user.
	 *
	 * @return	array
	 * @param	string[optional] $sinceId	Returns only sent direct messages with an id greater than (that is, more recent than) the specified $sinceId.
	 * @param	string[optional] $maxId	Returns only statuses with an ID less than (that is, older than) or equal to the specified $maxId.
	 * @param	int[optiona] $count	Specifies the number of direct messages to retrieve. May not be greater than 200.
	 * @param	int[optional] $page
	 */
	public function getSentDirectMessages($sinceId = null, $maxId = null, $count = null, $page = null)
	{
		// validate parameters
		if($sinceId !== null && (string) $sinceId == '') throw new TwitterException('Invalid value for sinceId.');
		if($maxId !== null && (string) $maxId == '') throw new TwitterException('Invalid value for maxId.');
		if($count !== null && (int) $count > 200) throw new TwitterException('Count can\'t be larger then 200.');

		// build url
		$aParameters = array();
		if($sinceId !== null) $aParameters['since_id'] = (string) $sinceId;
		if($maxId !== null) $aParameters['max_id'] = (string) $maxId;
		if($count !== null) $aParameters['count'] = (int) $count;
		if($page !== null) $aParameters['page'] = (int) $page;

		// do the call
		$response = $this->doCall('direct_messages/sent.xml', $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aDirectMessages = array();

		// loop statuses
		foreach ($xml->direct_message as $message) $aDirectMessages[] = $this->messageXMLToArray($message);

		// return
		return (array) $aDirectMessages;
	}


	/**
	 * Sends a new direct message to the specified user from the authenticating user.
	 *
	 * @return	array
	 * @param	string $id	The id or screen name of the recipient user.
	 * @param	string $text	The text of your direct message. Keep it under 140 characters.
	 */
	public function sendDirectMessage($id, $text)
	{
		// redefine
		$id = (string) $id;
		$text = (string) $text;

		// validate parameters
		if(strlen($text) > 140) throw new TwitterException('Maximum 140 characters allowed for status.');

		// build parameters
		$aParameters = array();
		$aParameters['user'] = $id;
		$aParameters['text'] = $text;

		// do the call
		$response = $this->doCall('direct_messages/new.xml', $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->messageXMLToArray($xml);
	}


	/**
	 * Destroys the direct message.
	 * The authenticating user must be the recipient of the specified direct message.
	 *
	 * @return	array
	 * @param	string $id
	 */
	public function deleteDirectMessage($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'direct_messages/destroy/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->messageXMLToArray($xml);
	}


// friendship methods
	/**
	 * Befriends the user specified in the id parameter as the authenticating user.
	 *
	 * @return	array
	 * @param	string $id	The id or screen name of the user to befriend.
	 * @param	bool[optional] $follow	Enable notifications for the target user in addition to becoming friends.
	 */
	public function createFriendship($id, $follow = true)
	{
		// redefine
		$id = (string) $id;
		$follow = (bool) $follow;

		// build url
		$url = 'friendships/create/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;
		if($follow) $aParameters['follow'] = $follow;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


	/**
	 * Discontinues friendship with the user.
	 *
	 * @return	array
	 * @param	string $id
	 */
	public function deleteFriendship($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'friendships/destroy/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


	/**
	 * Tests if a friendship exists between two users.
	 *
	 * @return	bool
	 * @param	string $id	The id or screen_name of the first user to test friendship for.
	 * @param	string $friendId	The id or screen_name of the second user to test friendship for.
	 */
	public function existsFriendship($id, $friendId)
	{
		// redefine
		$id = (string) $id;
		$friendId = (string) $friendId;

		// build parameters
		$aParameters = array();
		$aParameters['user_a'] = (string) $id;
		$aParameters['user_b'] = (string) $friendId;

		// do the call
		$response = $this->doCall('friendships/exists.xml', $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (bool) ($xml == 'true');
	}


	/**
	 * Returns detailed information about the relationship between two users.
	 *
	 * @return	array
	 * @param	string $id	The id or screen name of the subject user.
	 * @param	string $friendId	The id or screen name of the target user.
	 */
	public function getFriendship($id, $friendId)
	{
		// redefine
		$id = (string) $id;
		$friendId = (string) $friendId;

		// build parameters
		$aParameters = array();
		if((bool) preg_match("/^[0-9]+$/", $id)) $aParameters['source_id'] = $id;
		else $aParameters['source_screen_name'] = (string) $id;
		if((bool) preg_match("/^[0-9]+$/", $friendId)) $aParameters['target_id'] = $friendId;
		else $aParameters['target_screen_name'] = $friendId;

		// do the call
		$response = $this->doCall('friendships/show.xml', $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		$aReturn = array();
		$aReturn['target']['id'] = (string) $xml->target->id;
		$aReturn['target']['screen_name'] = (string) utf8_decode($xml->target->screen_name);
		$aReturn['target']['following'] = (bool) ((string) $xml->target->following == 'true');
		$aReturn['target']['followed_by'] = (bool) ((string) $xml->target->followed_by == 'true');

		$aReturn['source']['id'] = (string) $xml->source->id;
		$aReturn['source']['screen_name'] = (string) utf8_decode($xml->source->screen_name);
		$aReturn['source']['following'] = (bool) ((string) $xml->source->following == 'true');
		$aReturn['source']['followed_by'] = (bool) ((string) $xml->source->followed_by == 'true');
		$aReturn['source']['notifications_enabled'] = (bool) ((string) $xml->source->notifications_enabled == 'true');
		$aReturn['source']['blocking'] = (bool) ((string) $xml->source->blocking == 'true');

		// return
		return (array) $aReturn;
	}


// social grap methods
	/**
	 * Returns an array of numeric IDs for every user the specified user is following.
	 *
	 * @return	array
	 * @param	string[optional] $id	The id or screen name of the user for whom to request a list of friends.
	 * @param	int[optional] $page	Specifies the page number of the results beginning at 1. A single page contains 5000 ids. This is recommended for users with large ID lists. If not provided all ids are returned. (Please note that the result set isn't guaranteed to be 5000 every time as suspended users will be filtered out.)
	 */
	public function getFriendIds($id = null, $page = null)
	{
		// build parameters
		$aParameters = array();
		if($page !== null) $aParameters['page'] = (int) $page;

		// build url
		$url = 'friends/ids.xml';
		if($id !== null) $url = 'friends/ids/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aReturn = array();

		if(isset($xml->id))
		{
			// loop ids
			foreach($xml->id as $id) $aReturn[] = (string) $id;
		}

		// return
		return (array) $aReturn;
	}


	/**
	 * Returns an array of numeric IDs for every user following the specified user.
	 *
	 * @return	array
	 * @param	string[optional] $id	The id or screen name  of the user to retrieve the friends ID list for.
	 * @param	int[optional] $page	Specifies the page number of the results beginning at 1. A single page contains 5000 ids. This is recommended for users with large ID lists. If not provided all ids are returned. (Please note that the result set isn't guaranteed to be 5000 every time as suspended users will be filtered out.)
	 */
	public function getFollowerIds($id = null, $page = null)
	{
		// build parameters
		$aParameters = array();
		if($page !== null) $aParameters['page'] = (int) $page;

		// build url
		$url = 'followers/ids.xml';
		if($id !== null) $url = 'followers/ids/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aReturn = array();

		if(isset($xml->id))
		{
			// loop ids
			foreach($xml->id as $id) $aReturn[] = (string) $id;
		}

		// return
		return (array) $aReturn;
	}


// account methods
	/**
	 * Verifies your credentials
	 * Use this method to test if supplied user credentials are valid.
	 *
	 * @return	bool
	 */
	public function verifyCrendentials()
	{
		try
		{
			// do the call
			$response = $this->doCall('account/verify_credentials.xml', array(), true);

			// content was found
			if($response != '') return true;

			// no content
			else return false;
		}
		catch (Exception $e)
		{
			if($e->getCode() == 401 || $e->getMessage() == 'Could not authenticate you.') return false;
			else throw $e;
		}
	}


	/**
	 * Returns the remaining number of API requests available to the requesting user before the API limit is reached for the current hour.
	 *
	 * @return	array
	 */
	public function getRateLimitStatus()
	{
		// do the call
		$response = $this->doCall('account/rate_limit_status.xml', array(), true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// create response
		if(isset($xml->{'remaining-hits'})) $aResponse['remaining_hits'] = (int) $xml->{'remaining-hits'};
		if(isset($xml->{'reset-time-in-seconds'})) $aResponse['reset_time'] = (int) $xml->{'reset-time-in-seconds'};
		if(isset($xml->{'hourly-limit'})) $aResponse['hourly_limit'] = (int) $xml->{'hourly-limit'};

		// return
		return $aResponse;
	}


	/**
	 * Ends the session of the authenticating user, returning a null cookie.
	 * Use this method to sign users out of client-facing applications like widgets.
	 *
	 * @return	void
	 */
	public function endSession()
	{
		$this->doCall('account/end_session');
	}


	/**
	 * Sets which device Twitter delivers updates to for the authenticating user.
	 * Sending none as the device parameter will disable IM or SMS updates.
	 *
	 * @return	array
	 * @param	string $device	Must be one of: sms, im, none.
	 */
	public function updateDeliveryDevice($device)
	{
		// redefine
		$device = (string) $device;

		// init vars
		$aPossibleDevices = array('sms', 'im', 'none');

		// validate parameters
		if(!in_array($device, $aPossibleDevices)) throw new TwitterException('Invalid value for device. Possible values are: '. implode(', ', $aPossibleDevices) .'.');

		// build url
		$url = 'account/update_delivery_device.xml';

		// build parameters
		$aParameters = array();
		$aParameters['device'] = $device;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


	/**
	 * Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com.
	 * Only the parameters specified will be updated.
	 *
	 * @return	array
	 * @param	string[optiona] $backgroundColor
	 * @param	string[optiona] $textColor
	 * @param	string[optiona] $linkColor
	 * @param	string[optiona] $sidebarBackgroundColor
	 * @param	string[optiona] $sidebarBorderColor
	 */
	public function updateProfileColors($backgroundColor = null, $textColor = null, $linkColor = null, $sidebarBackgroundColor = null, $sidebarBorderColor = null)
	{
		// validate parameters
		if($backgroundColor === null && $textColor === null && $linkColor === null && $sidebarBackgroundColor === null && $sidebarBorderColor === null) throw new TwitterException('Specify at least one parameter.');
		if($backgroundColor !== null && (strlen($backgroundColor) < 3 || strlen($backgroundColor) > 6)) throw new TwitterException('Invalid color for background color.');
		if($textColor !== null && (strlen($textColor) < 3 || strlen($textColor) > 6)) throw new TwitterException('Invalid color for text color.');
		if($linkColor !== null && (strlen($linkColor) < 3 || strlen($linkColor) > 6)) throw new TwitterException('Invalid color for link color.');
		if($sidebarBackgroundColor !== null && (strlen($sidebarBackgroundColor) < 3 || strlen($sidebarBackgroundColor) > 6)) throw new TwitterException('Invalid color for sidebar background color.');
		if($sidebarBorderColor !== null && (strlen($sidebarBorderColor) < 3 || strlen($sidebarBorderColor) > 6)) throw new TwitterException('Invalid color for sidebar border color.');

		// build parameters
		if($backgroundColor !== null) $aParameters['profile_background_color'] = (string) $backgroundColor;
		if($textColor !== null) $aParameters['profile_text_color'] = (string) $textColor;
		if($linkColor !== null) $aParameters['profile_link_color'] = (string) $linkColor;
		if($sidebarBackgroundColor !== null) $aParameters['profile_sidebar_fill_color'] = (string) $sidebarBackgroundColor;
		if($sidebarBorderColor !== null) $aParameters['profile_sidebar_border_color'] = (string) $sidebarBorderColor;

		// make the call
		$response = $this->doCall('account/update_profile_colors.xml', $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml, true);
	}


	/**
	 * Updates the authenticating user's profile image.
	 * Expects raw multipart data, not a URL to an image.
	 *
	 * @remark	not implemented yet, feel free to code
	 * @return	void
	 * @param	string $image
	 */
	public function updateProfileImage($image)
	{
		throw new TwitterException(null, 501);

		// build parameters
		$aParameters = array();
		$aParameters['image'] = (string) $image;

		// make the call
		$response = $this->doCall('account/update_profile_image.xml', $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml, true);
	}


	/**
	 * Updates the authenticating user's profile background image.
	 * Expects raw multipart data, not a URL to an image.
	 *
	 * @remark	not implemented yet, feel free to code
	 * @return	void
	 * @param	string $image
	 */
	public function updateProfileBackgroundImage($image)
	{
		throw new TwitterException(null, 501);

		// build parameters
		$aParameters = array();
		$aParameters['image'] = (string) $image;

		// make the call
		$response = $this->doCall('account/update_profile_background_image.xml', $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml, true);
	}


	/**
	 * Sets values that users are able to set under the "Account" tab of their settings page.
	 * Only the parameters specified will be updated.
	 *
	 * @return	array
	 * @param	string[optional] $name
	 * @param	string[optional] $email
	 * @param	string[optional] $url
	 * @param	string[optional] $location
	 * @param	string[optional] $description
	 */
	public function updateProfile($name = null, $email = null, $url = null, $location = null, $description = null)
	{
		// validate parameters
		if($name === null && $email === null && $url === null && $location === null && $description === null) throw new TwitterException('Specify at least one parameter.');
		if($name !== null && strlen($name) > 40) throw new TwitterException('Maximum 40 characters allowed for name.');
		if($email !== null && strlen($email) > 40) throw new TwitterException('Maximum 40 characters allowed for email.');
		if($url !== null && strlen($url) > 100) throw new TwitterException('Maximum 100 characters allowed for url.');
		if($location !== null && strlen($location) > 30) throw new TwitterException('Maximum 30 characters allowed for location.');
		if($description !== null && strlen($description) > 160) throw new TwitterException('Maximum 160 characters allowed for description.');

		// build parameters
		if($name !== null) $aParameters['name'] = (string) $name;
		if($email !== null) $aParameters['email'] = (string) $email;
		if($url !== null) $aParameters['url'] = (string) $url;
		if($location !== null) $aParameters['location'] = (string) $location;
		if($description !== null) $aParameters['description'] = (string) $description;

		// make the call
		$response = $this->doCall('account/update_profile.xml', $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml, true);
	}


// favorite methods
	/**
	 * Returns the 20 most recent favorite statuses for the authenticating user or user specified by the $id parameter
	 *
	 * @return	array
	 * @param	string[optional] $id	The id or screen name of the user for whom to request a list of favorite statuses.
	 * @param	int[optional] $page
	 */
	public function getFavorites($id = null, $page = null)
	{
		// build parameters
		$aParameters = array();
		if($page !== null) $aParameters['page'] = (int) $page;

		$url = 'favorites.xml';
		if($id !== null) $url = 'favorites/'. urlencode($id) .'.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aStatuses = array();

		// loop statuses
		foreach ($xml->status as $status) $aStatuses[] = $this->statusXMLToArray($status);

		// return
		return (array) $aStatuses;
	}


	/**
	 * Favorites the status specified in the id parameter as the authenticating user.
	 *
	 * @return	array
	 * @param	string $id
	 */
	public function createFavorite($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'favorites/create/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->statusXMLToArray($xml);
	}


	/**
	 * Un-favorites the status specified in the id parameter as the authenticating user.
	 *
	 * @return	array
	 * @param	string $id
	 */
	public function deleteFavorite($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'favorites/destroy/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->statusXMLToArray($xml);
	}


// notification methods
	/**
	 * Enables notifications for updates from the specified user to the authenticating user.
	 * This method requires the authenticated user to already be friends with the specified user otherwise the error "there was a problem following the specified user" will be returned.
	 *
	 * @return	void
	 * @param	string $id
	 */
	public function follow($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'notifications/follow/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


	/**
	 * Disables notifications for updates from the specified user to the authenticating user.
	 * This method requires the authenticated user to already be friends with the specified user otherwise the error "there was a problem following the specified user" will be returned.
	 *
	 * @return	void
	 * @param	string $id
	 */
	public function unfollow($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'notifications/leave/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


// block methods
	/**
	 * Blocks the user specified in the id parameter as the authenticating user.
	 *
	 * @return	void
	 * @param	string $id
	 */
	public function createBlock($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'blocks/create/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


	/**
	 * Un-blocks the user specified in the id parameter as the authenticating user.
	 *
	 * @return	void
	 * @param	string $id
	 */
	public function deleteBlock($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'blocks/destroy/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		$response = $this->doCall($url, $aParameters, true);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return (array) $this->userXMLToArray($xml);
	}


	/**
	 * Returns if the authenticating user is blocking a target user.
	 *
	 * @return	bool
	 * @param	string $id	The id or screen_name of the potentially blocked user.
	 */
	public function existsBlock($id)
	{
		// redefine
		$id = (string) $id;

		// build url
		$url = 'blocks/exists/'. urlencode($id) .'.xml';

		// build parameters
		$aParameters = array();
		$aParameters['id'] = $id;

		// do the call
		try
		{
			$response = $this->doCall($url, $aParameters, true, false);
		}

		// catch exceptions
		catch(Exception $e)
		{
			// not blocking
			if($e->getMessage() == 'You are not blocking this user.') return false;

			// other exceptions
			else throw $e;
		}

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// return
		return true;
	}


	/**
	 * Returns an array of user that the authenticating user is blocking.
	 *
	 * @return	array
	 * @param	int[optional] $page	Specifies the page number of the results beginning at 1. A single page contains 20 ids.
	 */
	public function getBlocked($page = null)
	{
		// build parameters
		$aParameters = array();
		if($page !== null) $aParameters['page'] = (int) $page;

		// build url
		$url = 'blocks/blocking.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aUsers = array();

		// loop statuses
		foreach ($xml->user as $user) $aUsers[] = $this->userXMLToArray($user);

		// return
		return (array) $aUsers;
	}


	/**
	 * Returns an array of numeric user ids the authenticating user is blocking.
	 *
	 * @return	array
	 */
	public function getBlockedIds()
	{
		// build parameters
		$aParameters = array();

		// build url
		$url = 'blocks/blocking/ids.xml';

		// do the call
		$response = $this->doCall($url, $aParameters, true, false);

		// convert into xml-object
		$xml = @simplexml_load_string($response);

		// validate
		if($xml == false) throw new TwitterException('invalid body');

		// init var
		$aUsers = array();

		// loop statuses
		foreach ($xml->id as $id) $aUsers[] = (string) $id;

		// return
		return (array) $aUsers;
	}


// help methods
	/**
	 * Test the connection to Twitter
	 *
	 * @return	bool
	 */
	public function test()
	{
		// make the call
		$response = $this->doCall('help/test.xml');

		// validate response & return
		return (bool) ($response == '<ok>true</ok>');
	}
}


/**
 * Twitter Exception class
 *
 * @author	Tijs Verkoyen <php-twitter@verkoyen.eu>
 */
class TwitterException extends Exception
{
	/**
	 * Http header-codes
	 *
	 * @var	array
	 */
	private $aStatusCodes = array(100 => 'Continue',
									101 => 'Switching Protocols',
									200 => 'OK',
									201 => 'Created',
									202 => 'Accepted',
									203 => 'Non-Authoritative Information',
									204 => 'No Content',
									205 => 'Reset Content',
									206 => 'Partial Content',
									300 => 'Multiple Choices',
									301 => 'Moved Permanently',
									301 => 'Status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.',
									302 => 'Found',
									302 => 'Status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.',
									303 => 'See Other',
									304 => 'Not Modified',
									305 => 'Use Proxy',
									306 => '(Unused)',
									307 => 'Temporary Redirect',
									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',
									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',
									500 => 'Internal Server Error',
									501 => 'Not Implemented',
									502 => 'Bad Gateway',
									503 => 'Service Unavailable',
									504 => 'Gateway Timeout',
									505 => 'HTTP Version Not Supported');


	/**
	 * Default constructor
	 *
	 * @return	void
	 * @param	string[optional] $message
	 * @param	int[optional] $code
	 */
	public function __construct($message = null, $code = null)
	{
		// set message
		if($message === null && isset($this->aStatusCodes[(int) $code])) $message = $this->aStatusCodes[(int) $code];

		// call parent
		parent::__construct((string) $message, $code);
	}
}

?>