File indexing completed on 2024-05-12 06:03:06

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Service
0017  * @subpackage Twitter
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Http_Client
0025  */
0026 // require_once 'Zend/Http/Client.php';
0027 
0028 /**
0029  * @see Zend_Http_CookieJar
0030  */
0031 // require_once 'Zend/Http/CookieJar.php';
0032 
0033 /**
0034  * @see Zend_Oauth_Consumer
0035  */
0036 // require_once 'Zend/Oauth/Consumer.php';
0037 
0038 /**
0039  * @see Zend_Oauth_Token_Access
0040  */
0041 // require_once 'Zend/Oauth/Token/Access.php';
0042 
0043 /**
0044  * @see Zend_Service_Twitter_Response
0045  */
0046 // require_once 'Zend/Service/Twitter/Response.php';
0047 
0048 /**
0049  * @category   Zend
0050  * @package    Zend_Service
0051  * @subpackage Twitter
0052  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0053  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0054  */
0055 class Zend_Service_Twitter
0056 {
0057     /**
0058      * Base URI for all API calls
0059      */
0060     const API_BASE_URI = 'https://api.twitter.com/1.1/';
0061 
0062     /**
0063      * OAuth Endpoint
0064      */
0065     const OAUTH_BASE_URI = 'https://api.twitter.com/oauth';
0066 
0067     /**
0068      * 246 is the current limit for a status message, 140 characters are displayed
0069      * initially, with the remainder linked from the web UI or client. The limit is
0070      * applied to a html encoded UTF-8 string (i.e. entities are counted in the limit
0071      * which may appear unusual but is a security measure).
0072      *
0073      * This should be reviewed in the future...
0074      */
0075     const STATUS_MAX_CHARACTERS = 246;
0076 
0077     /**
0078      * @var array
0079      */
0080     protected $cookieJar;
0081 
0082     /**
0083      * Date format for 'since' strings
0084      *
0085      * @var string
0086      */
0087     protected $dateFormat = 'D, d M Y H:i:s T';
0088 
0089     /**
0090      * @var Zend_Http_Client
0091      */
0092     protected $httpClient = null;
0093 
0094     /**
0095      * Current method type (for method proxying)
0096      *
0097      * @var string
0098      */
0099     protected $methodType;
0100 
0101     /**
0102      * Oauth Consumer
0103      *
0104      * @var Zend_Oauth_Consumer
0105      */
0106     protected $oauthConsumer = null;
0107 
0108     /**
0109      * Types of API methods
0110      *
0111      * @var array
0112      */
0113     protected $methodTypes = array(
0114         'account',
0115         'application',
0116         'blocks',
0117         'directmessages',
0118         'favorites',
0119         'friendships',
0120         'search',
0121         'statuses',
0122         'users',
0123     );
0124 
0125     /**
0126      * Options passed to constructor
0127      *
0128      * @var array
0129      */
0130     protected $options = array();
0131 
0132     /**
0133      * Username
0134      *
0135      * @var string
0136      */
0137     protected $username;
0138 
0139     /**
0140      * Constructor
0141      *
0142      * @param  null|array|Zend_Config $options
0143      * @param  null|Zend_Oauth_Consumer $consumer
0144      * @param  null|Zend_Http_Client $httpClient
0145      */
0146     public function __construct($options = null, Zend_Oauth_Consumer $consumer = null, Zend_Http_Client $httpClient = null)
0147     {
0148         if ($options instanceof Zend_Config) {
0149             $options = $options->toArray();
0150         }
0151         if (!is_array($options)) {
0152             $options = array();
0153         }
0154 
0155         $this->options = $options;
0156 
0157         if (isset($options['username'])) {
0158             $this->setUsername($options['username']);
0159         }
0160 
0161         $accessToken = false;
0162         if (isset($options['accessToken'])) {
0163             $accessToken = $options['accessToken'];
0164         } elseif (isset($options['access_token'])) {
0165             $accessToken = $options['access_token'];
0166         }
0167 
0168         $oauthOptions = array();
0169         if (isset($options['oauthOptions'])) {
0170             $oauthOptions = $options['oauthOptions'];
0171         } elseif (isset($options['oauth_options'])) {
0172             $oauthOptions = $options['oauth_options'];
0173         }
0174         $oauthOptions['siteUrl'] = self::OAUTH_BASE_URI;
0175 
0176         $httpClientOptions = array();
0177         if (isset($options['httpClientOptions'])) {
0178             $httpClientOptions = $options['httpClientOptions'];
0179         } elseif (isset($options['http_client_options'])) {
0180             $httpClientOptions = $options['http_client_options'];
0181         }
0182 
0183         // If we have an OAuth access token, use the HTTP client it provides
0184         if ($accessToken && is_array($accessToken)
0185             && (isset($accessToken['token']) && isset($accessToken['secret']))
0186         ) {
0187             $token = new Zend_Oauth_Token_Access();
0188             $token->setToken($accessToken['token']);
0189             $token->setTokenSecret($accessToken['secret']);
0190             $accessToken = $token;
0191         }
0192         if ($accessToken && $accessToken instanceof Zend_Oauth_Token_Access) {
0193             $oauthOptions['token'] = $accessToken;
0194             $this->setHttpClient($accessToken->getHttpClient($oauthOptions, self::OAUTH_BASE_URI, $httpClientOptions));
0195             return;
0196         }
0197 
0198         // See if we were passed an http client
0199         if (isset($options['httpClient']) && null === $httpClient) {
0200             $httpClient = $options['httpClient'];
0201         } elseif (isset($options['http_client']) && null === $httpClient) {
0202             $httpClient = $options['http_client'];
0203         }
0204         if ($httpClient instanceof Zend_Http_Client) {
0205             $this->httpClient = $httpClient;
0206         } else {
0207             $this->setHttpClient(new Zend_Http_Client(null, $httpClientOptions));
0208         }
0209 
0210         // Set the OAuth consumer
0211         if ($consumer === null) {
0212             $consumer = new Zend_Oauth_Consumer($oauthOptions);
0213         }
0214         $this->oauthConsumer = $consumer;
0215     }
0216 
0217     /**
0218      * Proxy service methods
0219      *
0220      * @param  string $type
0221      * @return Twitter
0222      * @throws Exception\DomainException If method not in method types list
0223      */
0224     public function __get($type)
0225     {
0226         $type = strtolower($type);
0227         $type = str_replace('_', '', $type);
0228         if (!in_array($type, $this->methodTypes)) {
0229             // require_once 'Zend/Service/Twitter/Exception.php';
0230             throw new Zend_Service_Twitter_Exception(
0231                 'Invalid method type "' . $type . '"'
0232             );
0233         }
0234         $this->methodType = $type;
0235         return $this;
0236     }
0237 
0238     /**
0239      * Method overloading
0240      *
0241      * @param  string $method
0242      * @param  array $params
0243      * @return mixed
0244      * @throws Exception\BadMethodCallException if unable to find method
0245      */
0246     public function __call($method, $params)
0247     {
0248         if (method_exists($this->oauthConsumer, $method)) {
0249             $return = call_user_func_array(array($this->oauthConsumer, $method), $params);
0250             if ($return instanceof Zend_Oauth_Token_Access) {
0251                 $this->setHttpClient($return->getHttpClient($this->options));
0252             }
0253             return $return;
0254         }
0255         if (empty($this->methodType)) {
0256             // require_once 'Zend/Service/Twitter/Exception.php';
0257             throw new Zend_Service_Twitter_Exception(
0258                 'Invalid method "' . $method . '"'
0259             );
0260         }
0261 
0262         $test = str_replace('_', '', strtolower($method));
0263         $test = $this->methodType . $test;
0264         if (!method_exists($this, $test)) {
0265             // require_once 'Zend/Service/Twitter/Exception.php';
0266             throw new Zend_Service_Twitter_Exception(
0267                 'Invalid method "' . $test . '"'
0268             );
0269         }
0270 
0271         return call_user_func_array(array($this, $test), $params);
0272     }
0273 
0274     /**
0275      * Set HTTP client
0276      *
0277      * @param Zend_Http_Client $client
0278      * @return self
0279      */
0280     public function setHttpClient(Zend_Http_Client $client)
0281     {
0282         $this->httpClient = $client;
0283         $this->httpClient->setHeaders(array('Accept-Charset' => 'ISO-8859-1,utf-8'));
0284         return $this;
0285     }
0286 
0287     /**
0288      * Get the HTTP client
0289      *
0290      * Lazy loads one if none present
0291      *
0292      * @return Zend_Http_Client
0293      */
0294     public function getHttpClient()
0295     {
0296         if (null === $this->httpClient) {
0297             $this->setHttpClient(new Zend_Http_Client());
0298         }
0299         return $this->httpClient;
0300     }
0301 
0302     /**
0303      * Retrieve username
0304      *
0305      * @return string
0306      */
0307     public function getUsername()
0308     {
0309         return $this->username;
0310     }
0311 
0312     /**
0313      * Set username
0314      *
0315      * @param  string $value
0316      * @return self
0317      */
0318     public function setUsername($value)
0319     {
0320         $this->username = $value;
0321         return $this;
0322     }
0323 
0324     /**
0325      * Checks for an authorised state
0326      *
0327      * @return bool
0328      */
0329     public function isAuthorised()
0330     {
0331         if ($this->getHttpClient() instanceof Zend_Oauth_Client) {
0332             return true;
0333         }
0334         return false;
0335     }
0336 
0337     /**
0338      * Verify Account Credentials
0339      *
0340      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0341      * @throws Exception\DomainException if unable to decode JSON payload
0342      * @return Zend_Service_Twitter_Response
0343      */
0344     public function accountVerifyCredentials()
0345     {
0346         $this->init();
0347         $response = $this->get('account/verify_credentials');
0348         return new Zend_Service_Twitter_Response($response);
0349     }
0350 
0351     /**
0352      * Returns the number of api requests you have left per hour.
0353      *
0354      * @todo   Have a separate payload object to represent rate limits
0355      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0356      * @throws Exception\DomainException if unable to decode JSON payload
0357      * @return Zend_Service_Twitter_Response
0358      */
0359     public function applicationRateLimitStatus()
0360     {
0361         $this->init();
0362         $response = $this->get('application/rate_limit_status');
0363         return new Zend_Service_Twitter_Response($response);
0364     }
0365 
0366     /**
0367      * Blocks the user specified in the ID parameter as the authenticating user.
0368      * Destroys a friendship to the blocked user if it exists.
0369      *
0370      * @param  integer|string $id       The ID or screen name of a user to block.
0371      * @throws Exception\DomainException if unable to decode JSON payload
0372      * @return Zend_Service_Twitter_Response
0373      */
0374     public function blocksCreate($id)
0375     {
0376         $this->init();
0377         $path     = 'blocks/create';
0378         $params   = $this->createUserParameter($id, array());
0379         $response = $this->post($path, $params);
0380         return new Zend_Service_Twitter_Response($response);
0381     }
0382 
0383     /**
0384      * Un-blocks the user specified in the ID parameter for the authenticating user
0385      *
0386      * @param  integer|string $id       The ID or screen_name of the user to un-block.
0387      * @throws Exception\DomainException if unable to decode JSON payload
0388      * @return Zend_Service_Twitter_Response
0389      */
0390     public function blocksDestroy($id)
0391     {
0392         $this->init();
0393         $path   = 'blocks/destroy';
0394         $params = $this->createUserParameter($id, array());
0395         $response = $this->post($path, $params);
0396         return new Zend_Service_Twitter_Response($response);
0397     }
0398 
0399     /**
0400      * Returns an array of user ids that the authenticating user is blocking
0401      *
0402      * @param  integer $cursor  Optional. Specifies the cursor position at which to begin listing ids; defaults to first "page" of results.
0403      * @throws Exception\DomainException if unable to decode JSON payload
0404      * @return Zend_Service_Twitter_Response
0405      */
0406     public function blocksIds($cursor = -1)
0407     {
0408         $this->init();
0409         $path = 'blocks/ids';
0410         $response = $this->get($path, array('cursor' => $cursor));
0411         return new Zend_Service_Twitter_Response($response);
0412     }
0413 
0414     /**
0415      * Returns an array of user objects that the authenticating user is blocking
0416      *
0417      * @param  integer $cursor  Optional. Specifies the cursor position at which to begin listing ids; defaults to first "page" of results.
0418      * @throws Exception\DomainException if unable to decode JSON payload
0419      * @return Zend_Service_Twitter_Response
0420      */
0421     public function blocksList($cursor = -1)
0422     {
0423         $this->init();
0424         $path = 'blocks/list';
0425         $response = $this->get($path, array('cursor' => $cursor));
0426         return new Zend_Service_Twitter_Response($response);
0427     }
0428 
0429     /**
0430      * Destroy a direct message
0431      *
0432      * @param  int $id ID of message to destroy
0433      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0434      * @throws Exception\DomainException if unable to decode JSON payload
0435      * @return Zend_Service_Twitter_Response
0436      */
0437     public function directMessagesDestroy($id)
0438     {
0439         $this->init();
0440         $path     = 'direct_messages/destroy';
0441         $params   = array('id' => $this->validInteger($id));
0442         $response = $this->post($path, $params);
0443         return new Zend_Service_Twitter_Response($response);
0444     }
0445 
0446     /**
0447      * Retrieve direct messages for the current user
0448      *
0449      * $options may include one or more of the following keys
0450      * - count: return page X of results
0451      * - since_id: return statuses only greater than the one specified
0452      * - max_id: return statuses with an ID less than (older than) or equal to that specified
0453      * - include_entities: setting to false will disable embedded entities
0454      * - skip_status:setting to true, "t", or 1 will omit the status in returned users
0455      *
0456      * @param  array $options
0457      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0458      * @throws Exception\DomainException if unable to decode JSON payload
0459      * @return Zend_Service_Twitter_Response
0460      */
0461     public function directMessagesMessages(array $options = array())
0462     {
0463         $this->init();
0464         $path   = 'direct_messages';
0465         $params = array();
0466         foreach ($options as $key => $value) {
0467             switch (strtolower($key)) {
0468                 case 'count':
0469                     $params['count'] = (int) $value;
0470                     break;
0471                 case 'since_id':
0472                     $params['since_id'] = $this->validInteger($value);
0473                     break;
0474                 case 'max_id':
0475                     $params['max_id'] = $this->validInteger($value);
0476                     break;
0477                 case 'include_entities':
0478                     $params['include_entities'] = (bool) $value;
0479                     break;
0480                 case 'skip_status':
0481                     $params['skip_status'] = (bool) $value;
0482                     break;
0483                 default:
0484                     break;
0485             }
0486         }
0487         $response = $this->get($path, $params);
0488         return new Zend_Service_Twitter_Response($response);
0489     }
0490 
0491     /**
0492      * Send a direct message to a user
0493      *
0494      * @param  int|string $user User to whom to send message
0495      * @param  string $text Message to send to user
0496      * @throws Exception\InvalidArgumentException if message is empty
0497      * @throws Exception\OutOfRangeException if message is too long
0498      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0499      * @throws Exception\DomainException if unable to decode JSON payload
0500      * @return Zend_Service_Twitter_Response
0501      */
0502     public function directMessagesNew($user, $text)
0503     {
0504         $this->init();
0505         $path = 'direct_messages/new';
0506 
0507         $len = iconv_strlen($text, 'UTF-8');
0508         if (0 == $len) {
0509             // require_once 'Zend/Service/Twitter/Exception.php';
0510             throw new Zend_Service_Twitter_Exception(
0511                 'Direct message must contain at least one character'
0512             );
0513         } elseif (140 < $len) {
0514             // require_once 'Zend/Service/Twitter/Exception.php';
0515             throw new Zend_Service_Twitter_Exception(
0516                 'Direct message must contain no more than 140 characters'
0517             );
0518         }
0519 
0520         $params         = $this->createUserParameter($user, array());
0521         $params['text'] = $text;
0522         $response       = $this->post($path, $params);
0523         return new Zend_Service_Twitter_Response($response);
0524     }
0525 
0526     /**
0527      * Retrieve list of direct messages sent by current user
0528      *
0529      * $options may include one or more of the following keys
0530      * - count: return page X of results
0531      * - page: return starting at page
0532      * - since_id: return statuses only greater than the one specified
0533      * - max_id: return statuses with an ID less than (older than) or equal to that specified
0534      * - include_entities: setting to false will disable embedded entities
0535      *
0536      * @param  array $options
0537      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0538      * @throws Exception\DomainException if unable to decode JSON payload
0539      * @return Zend_Service_Twitter_Response
0540      */
0541     public function directMessagesSent(array $options = array())
0542     {
0543         $this->init();
0544         $path   = 'direct_messages/sent';
0545         $params = array();
0546         foreach ($options as $key => $value) {
0547             switch (strtolower($key)) {
0548                 case 'count':
0549                     $params['count'] = (int) $value;
0550                     break;
0551                 case 'page':
0552                     $params['page'] = (int) $value;
0553                     break;
0554                 case 'since_id':
0555                     $params['since_id'] = $this->validInteger($value);
0556                     break;
0557                 case 'max_id':
0558                     $params['max_id'] = $this->validInteger($value);
0559                     break;
0560                 case 'include_entities':
0561                     $params['include_entities'] = (bool) $value;
0562                     break;
0563                 default:
0564                     break;
0565             }
0566         }
0567         $response = $this->get($path, $params);
0568         return new Zend_Service_Twitter_Response($response);
0569     }
0570 
0571     /**
0572      * Mark a status as a favorite
0573      *
0574      * @param  int $id Status ID you want to mark as a favorite
0575      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0576      * @throws Exception\DomainException if unable to decode JSON payload
0577      * @return Zend_Service_Twitter_Response
0578      */
0579     public function favoritesCreate($id)
0580     {
0581         $this->init();
0582         $path     = 'favorites/create';
0583         $params   = array('id' => $this->validInteger($id));
0584         $response = $this->post($path, $params);
0585         return new Zend_Service_Twitter_Response($response);
0586     }
0587 
0588     /**
0589      * Remove a favorite
0590      *
0591      * @param  int $id Status ID you want to de-list as a favorite
0592      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0593      * @throws Exception\DomainException if unable to decode JSON payload
0594      * @return Zend_Service_Twitter_Response
0595      */
0596     public function favoritesDestroy($id)
0597     {
0598         $this->init();
0599         $path     = 'favorites/destroy';
0600         $params   = array('id' => $this->validInteger($id));
0601         $response = $this->post($path, $params);
0602         return new Zend_Service_Twitter_Response($response);
0603     }
0604 
0605     /**
0606      * Fetch favorites
0607      *
0608      * $options may contain one or more of the following:
0609      * - user_id: Id of a user for whom to fetch favorites
0610      * - screen_name: Screen name of a user for whom to fetch favorites
0611      * - count: number of tweets to attempt to retrieve, up to 200
0612      * - since_id: return results only after the specified tweet id
0613      * - max_id: return results with an ID less than (older than) or equal to the specified ID
0614      * - include_entities: when set to false, entities member will be omitted
0615      *
0616      * @param  array $params
0617      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0618      * @throws Exception\DomainException if unable to decode JSON payload
0619      * @return Zend_Service_Twitter_Response
0620      */
0621     public function favoritesList(array $options = array())
0622     {
0623         $this->init();
0624         $path = 'favorites/list';
0625         $params = array();
0626         foreach ($options as $key => $value) {
0627             switch (strtolower($key)) {
0628                 case 'user_id':
0629                     $params['user_id'] = $this->validInteger($value);
0630                     break;
0631                 case 'screen_name':
0632                     $params['screen_name'] = $value;
0633                     break;
0634                 case 'count':
0635                     $params['count'] = (int) $value;
0636                     break;
0637                 case 'since_id':
0638                     $params['since_id'] = $this->validInteger($value);
0639                     break;
0640                 case 'max_id':
0641                     $params['max_id'] = $this->validInteger($value);
0642                     break;
0643                 case 'include_entities':
0644                     $params['include_entities'] = (bool) $value;
0645                     break;
0646                 default:
0647                     break;
0648             }
0649         }
0650         $response = $this->get($path, $params);
0651         return new Zend_Service_Twitter_Response($response);
0652     }
0653 
0654     /**
0655      * Create friendship
0656      *
0657      * @param  int|string $id User ID or name of new friend
0658      * @param  array $params Additional parameters to pass
0659      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0660      * @throws Exception\DomainException if unable to decode JSON payload
0661      * @return Zend_Service_Twitter_Response
0662      */
0663     public function friendshipsCreate($id, array $params = array())
0664     {
0665         $this->init();
0666         $path    = 'friendships/create';
0667         $params  = $this->createUserParameter($id, $params);
0668         $allowed = array(
0669             'user_id'     => null,
0670             'screen_name' => null,
0671             'follow'      => null,
0672         );
0673         $params = array_intersect_key($params, $allowed);
0674         $response = $this->post($path, $params);
0675         return new Zend_Service_Twitter_Response($response);
0676     }
0677 
0678     /**
0679      * Destroy friendship
0680      *
0681      * @param  int|string $id User ID or name of friend to remove
0682      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0683      * @throws Exception\DomainException if unable to decode JSON payload
0684      * @return Zend_Service_Twitter_Response
0685      */
0686     public function friendshipsDestroy($id)
0687     {
0688         $this->init();
0689         $path     = 'friendships/destroy';
0690         $params   = $this->createUserParameter($id, array());
0691         $response = $this->post($path, $params);
0692         return new Zend_Service_Twitter_Response($response);
0693     }
0694 
0695     /**
0696      * Search tweets
0697      *
0698      * $options may include any of the following:
0699      * - geocode: a string of the form "latitude, longitude, radius"
0700      * - lang: restrict tweets to the two-letter language code
0701      * - locale: query is in the given two-letter language code
0702      * - result_type: what type of results to receive: mixed, recent, or popular
0703      * - count: number of tweets to return per page; up to 100
0704      * - until: return tweets generated before the given date
0705      * - since_id: return resutls with an ID greater than (more recent than) the given ID
0706      * - max_id: return results with an ID less than (older than) the given ID
0707      * - include_entities: whether or not to include embedded entities
0708      *
0709      * @param  string $query
0710      * @param  array $options
0711      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0712      * @throws Exception\DomainException if unable to decode JSON payload
0713      * @return Zend_Service_Twitter_Response
0714      */
0715     public function searchTweets($query, array $options = array())
0716     {
0717         $this->init();
0718         $path = 'search/tweets';
0719 
0720         $len = iconv_strlen($query, 'UTF-8');
0721         if (0 == $len) {
0722             // require_once 'Zend/Service/Twitter/Exception.php';
0723             throw new Zend_Service_Twitter_Exception(
0724                 'Query must contain at least one character'
0725             );
0726         }
0727 
0728         $params = array('q' => $query);
0729         foreach ($options as $key => $value) {
0730             switch (strtolower($key)) {
0731                 case 'geocode':
0732                     if (!substr_count($value, ',') !== 2) {
0733                         // require_once 'Zend/Service/Twitter/Exception.php';
0734                         throw new Zend_Service_Twitter_Exception(
0735                             '"geocode" must be of the format "latitude,longitude,radius"'
0736                         );
0737                     }
0738                     list($latitude, $longitude, $radius) = explode(',', $value);
0739                     $radius = trim($radius);
0740                     if (!preg_match('/^\d+(mi|km)$/', $radius)) {
0741                         // require_once 'Zend/Service/Twitter/Exception.php';
0742                         throw new Zend_Service_Twitter_Exception(
0743                             'Radius segment of "geocode" must be of the format "[unit](mi|km)"'
0744                         );
0745                     }
0746                     $latitude  = (float) $latitude;
0747                     $longitude = (float) $longitude;
0748                     $params['geocode'] = $latitude . ',' . $longitude . ',' . $radius;
0749                     break;
0750                 case 'lang':
0751                     if (strlen($value) > 2) {
0752                         // require_once 'Zend/Service/Twitter/Exception.php';
0753                         throw new Zend_Service_Twitter_Exception(
0754                             'Query language must be a 2 character string'
0755                         );
0756                     }
0757                     $params['lang'] = strtolower($value);
0758                     break;
0759                 case 'locale':
0760                     if (strlen($value) > 2) {
0761                         // require_once 'Zend/Service/Twitter/Exception.php';
0762                         throw new Zend_Service_Twitter_Exception(
0763                             'Query locale must be a 2 character string'
0764                         );
0765                     }
0766                     $params['locale'] = strtolower($value);
0767                     break;
0768                 case 'result_type':
0769                     $value = strtolower($value);
0770                     if (!in_array($value, array('mixed', 'recent', 'popular'))) {
0771                         // require_once 'Zend/Service/Twitter/Exception.php';
0772                         throw new Zend_Service_Twitter_Exception(
0773                             'result_type must be one of "mixed", "recent", or "popular"'
0774                         );
0775                     }
0776                     $params['result_type'] = $value;
0777                     break;
0778                 case 'count':
0779                     $value = (int) $value;
0780                     if (1 > $value || 100 < $value) {
0781                         // require_once 'Zend/Service/Twitter/Exception.php';
0782                         throw new Zend_Service_Twitter_Exception(
0783                             'count must be between 1 and 100'
0784                         );
0785                     }
0786                     $params['count'] = $value;
0787                     break;
0788                 case 'until':
0789                     if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
0790                         // require_once 'Zend/Service/Twitter/Exception.php';
0791                         throw new Zend_Service_Twitter_Exception(
0792                             '"until" must be a date in the format YYYY-MM-DD'
0793                         );
0794                     }
0795                     $params['until'] = $value;
0796                     break;
0797                 case 'since_id':
0798                     $params['since_id'] = $this->validInteger($value);
0799                     break;
0800                 case 'max_id':
0801                     $params['max_id'] = $this->validInteger($value);
0802                     break;
0803                 case 'include_entities':
0804                     $params['include_entities'] = (bool) $value;
0805                     break;
0806                 default:
0807                     break;
0808             }
0809         }
0810         $response = $this->get($path, $params);
0811         return new Zend_Service_Twitter_Response($response);
0812     }
0813 
0814     /**
0815      * Destroy a status message
0816      *
0817      * @param  int $id ID of status to destroy
0818      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0819      * @throws Exception\DomainException if unable to decode JSON payload
0820      * @return Zend_Service_Twitter_Response
0821      */
0822     public function statusesDestroy($id)
0823     {
0824         $this->init();
0825         $path = 'statuses/destroy/' . $this->validInteger($id);
0826         $response = $this->post($path);
0827         return new Zend_Service_Twitter_Response($response);
0828     }
0829 
0830     /**
0831      * Friend Timeline Status
0832      *
0833      * $options may include one or more of the following keys
0834      * - count: number of tweets to attempt to retrieve, up to 200
0835      * - since_id: return results only after the specified tweet id
0836      * - max_id: return results with an ID less than (older than) or equal to the specified ID
0837      * - trim_user: when set to true, "t", or 1, user object in tweets will include only author's ID.
0838      * - contributor_details: when set to true, includes screen_name of each contributor
0839      * - include_entities: when set to false, entities member will be omitted
0840      * - exclude_replies: when set to true, will strip replies appearing in the timeline
0841      *
0842      * @param  array $params
0843      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0844      * @throws Exception\DomainException if unable to decode JSON payload
0845      * @return Zend_Service_Twitter_Response
0846      */
0847     public function statusesHomeTimeline(array $options = array())
0848     {
0849         $this->init();
0850         $path = 'statuses/home_timeline';
0851         $params = array();
0852         foreach ($options as $key => $value) {
0853             switch (strtolower($key)) {
0854                 case 'count':
0855                     $params['count'] = (int) $value;
0856                     break;
0857                 case 'since_id':
0858                     $params['since_id'] = $this->validInteger($value);
0859                     break;
0860                 case 'max_id':
0861                     $params['max_id'] = $this->validInteger($value);
0862                     break;
0863                 case 'trim_user':
0864                     if (in_array($value, array(true, 'true', 't', 1, '1'))) {
0865                         $value = true;
0866                     } else {
0867                         $value = false;
0868                     }
0869                     $params['trim_user'] = $value;
0870                     break;
0871                 case 'contributor_details:':
0872                     $params['contributor_details:'] = (bool) $value;
0873                     break;
0874                 case 'include_entities':
0875                     $params['include_entities'] = (bool) $value;
0876                     break;
0877                 case 'exclude_replies':
0878                     $params['exclude_replies'] = (bool) $value;
0879                     break;
0880                 default:
0881                     break;
0882             }
0883         }
0884         $response = $this->get($path, $params);
0885         return new Zend_Service_Twitter_Response($response);
0886     }
0887 
0888     /**
0889      * Get status replies
0890      *
0891      * $options may include one or more of the following keys
0892      * - count: number of tweets to attempt to retrieve, up to 200
0893      * - since_id: return results only after the specified tweet id
0894      * - max_id: return results with an ID less than (older than) or equal to the specified ID
0895      * - trim_user: when set to true, "t", or 1, user object in tweets will include only author's ID.
0896      * - contributor_details: when set to true, includes screen_name of each contributor
0897      * - include_entities: when set to false, entities member will be omitted
0898      *
0899      * @param  array $options
0900      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0901      * @throws Exception\DomainException if unable to decode JSON payload
0902      * @return Zend_Service_Twitter_Response
0903      */
0904     public function statusesMentionsTimeline(array $options = array())
0905     {
0906         $this->init();
0907         $path   = 'statuses/mentions_timeline';
0908         $params = array();
0909         foreach ($options as $key => $value) {
0910             switch (strtolower($key)) {
0911                 case 'count':
0912                     $params['count'] = (int) $value;
0913                     break;
0914                 case 'since_id':
0915                     $params['since_id'] = $this->validInteger($value);
0916                     break;
0917                 case 'max_id':
0918                     $params['max_id'] = $this->validInteger($value);
0919                     break;
0920                 case 'trim_user':
0921                     if (in_array($value, array(true, 'true', 't', 1, '1'))) {
0922                         $value = true;
0923                     } else {
0924                         $value = false;
0925                     }
0926                     $params['trim_user'] = $value;
0927                     break;
0928                 case 'contributor_details:':
0929                     $params['contributor_details:'] = (bool) $value;
0930                     break;
0931                 case 'include_entities':
0932                     $params['include_entities'] = (bool) $value;
0933                     break;
0934                 default:
0935                     break;
0936             }
0937         }
0938         $response = $this->get($path, $params);
0939         return new Zend_Service_Twitter_Response($response);
0940     }
0941 
0942     /**
0943      * Public Timeline status
0944      *
0945      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0946      * @throws Exception\DomainException if unable to decode JSON payload
0947      * @return Zend_Service_Twitter_Response
0948      */
0949     public function statusesSample()
0950     {
0951         $this->init();
0952         $path = 'statuses/sample';
0953         $response = $this->get($path);
0954         return new Zend_Service_Twitter_Response($response);
0955     }
0956 
0957     /**
0958      * Show a single status
0959      *
0960      * @param  int $id Id of status to show
0961      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0962      * @throws Exception\DomainException if unable to decode JSON payload
0963      * @return Zend_Service_Twitter_Response
0964      */
0965     public function statusesShow($id)
0966     {
0967         $this->init();
0968         $path = 'statuses/show/' . $this->validInteger($id);
0969         $response = $this->get($path);
0970         return new Zend_Service_Twitter_Response($response);
0971     }
0972 
0973     /**
0974      * Update user's current status
0975      *
0976      * @todo   Support additional parameters supported by statuses/update endpoint
0977      * @param  string $status
0978      * @param  null|int $inReplyToStatusId
0979      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
0980      * @throws Exception\OutOfRangeException if message is too long
0981      * @throws Exception\InvalidArgumentException if message is empty
0982      * @throws Exception\DomainException if unable to decode JSON payload
0983      * @return Zend_Service_Twitter_Response
0984      */
0985     public function statusesUpdate($status, $inReplyToStatusId = null)
0986     {
0987         $this->init();
0988         $path = 'statuses/update';
0989         $len = iconv_strlen(htmlspecialchars($status, ENT_QUOTES, 'UTF-8'), 'UTF-8');
0990         if ($len > self::STATUS_MAX_CHARACTERS) {
0991             // require_once 'Zend/Service/Twitter/Exception.php';
0992             throw new Zend_Service_Twitter_Exception(
0993                 'Status must be no more than '
0994                 . self::STATUS_MAX_CHARACTERS
0995                 . ' characters in length'
0996             );
0997         } elseif (0 == $len) {
0998             // require_once 'Zend/Service/Twitter/Exception.php';
0999             throw new Zend_Service_Twitter_Exception(
1000                 'Status must contain at least one character'
1001             );
1002         }
1003 
1004         $params = array('status' => $status);
1005         $inReplyToStatusId = $this->validInteger($inReplyToStatusId);
1006         if ($inReplyToStatusId) {
1007             $params['in_reply_to_status_id'] = $inReplyToStatusId;
1008         }
1009         $response = $this->post($path, $params);
1010         return new Zend_Service_Twitter_Response($response);
1011     }
1012 
1013     /**
1014      * User Timeline status
1015      *
1016      * $options may include one or more of the following keys
1017      * - user_id: Id of a user for whom to fetch favorites
1018      * - screen_name: Screen name of a user for whom to fetch favorites
1019      * - count: number of tweets to attempt to retrieve, up to 200
1020      * - since_id: return results only after the specified tweet id
1021      * - max_id: return results with an ID less than (older than) or equal to the specified ID
1022      * - trim_user: when set to true, "t", or 1, user object in tweets will include only author's ID.
1023      * - exclude_replies: when set to true, will strip replies appearing in the timeline
1024      * - contributor_details: when set to true, includes screen_name of each contributor
1025      * - include_rts: when set to false, will strip native retweets
1026      *
1027      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
1028      * @throws Exception\DomainException if unable to decode JSON payload
1029      * @return Zend_Service_Twitter_Response
1030      */
1031     public function statusesUserTimeline(array $options = array())
1032     {
1033         $this->init();
1034         $path = 'statuses/user_timeline';
1035         $params = array();
1036         foreach ($options as $key => $value) {
1037             switch (strtolower($key)) {
1038                 case 'user_id':
1039                     $params['user_id'] = $this->validInteger($value);
1040                     break;
1041                 case 'screen_name':
1042                     $params['screen_name'] = $this->validateScreenName($value);
1043                     break;
1044                 case 'count':
1045                     $params['count'] = (int) $value;
1046                     break;
1047                 case 'since_id':
1048                     $params['since_id'] = $this->validInteger($value);
1049                     break;
1050                 case 'max_id':
1051                     $params['max_id'] = $this->validInteger($value);
1052                     break;
1053                 case 'trim_user':
1054                     if (in_array($value, array(true, 'true', 't', 1, '1'))) {
1055                         $value = true;
1056                     } else {
1057                         $value = false;
1058                     }
1059                     $params['trim_user'] = $value;
1060                     break;
1061                 case 'contributor_details:':
1062                     $params['contributor_details:'] = (bool) $value;
1063                     break;
1064                 case 'exclude_replies':
1065                     $params['exclude_replies'] = (bool) $value;
1066                     break;
1067                 case 'include_rts':
1068                     $params['include_rts'] = (bool) $value;
1069                     break;
1070                 default:
1071                     break;
1072             }
1073         }
1074         $response = $this->get($path, $params);
1075         return new Zend_Service_Twitter_Response($response);
1076     }
1077 
1078     /**
1079      * Search users
1080      *
1081      * $options may include any of the following:
1082      * - page: the page of results to retrieve
1083      * - count: the number of users to retrieve per page; max is 20
1084      * - include_entities: if set to boolean true, include embedded entities
1085      *
1086      * @param  string $query
1087      * @param  array $options
1088      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
1089      * @throws Exception\DomainException if unable to decode JSON payload
1090      * @return Zend_Service_Twitter_Response
1091      */
1092     public function usersSearch($query, array $options = array())
1093     {
1094         $this->init();
1095         $path = 'users/search';
1096 
1097         $len = iconv_strlen($query, 'UTF-8');
1098         if (0 == $len) {
1099             // require_once 'Zend/Service/Twitter/Exception.php';
1100             throw new Zend_Service_Twitter_Exception(
1101                 'Query must contain at least one character'
1102             );
1103         }
1104 
1105         $params = array('q' => $query);
1106         foreach ($options as $key => $value) {
1107             switch (strtolower($key)) {
1108                 case 'count':
1109                     $value = (int) $value;
1110                     if (1 > $value || 20 < $value) {
1111                         // require_once 'Zend/Service/Twitter/Exception.php';
1112                         throw new Zend_Service_Twitter_Exception(
1113                             'count must be between 1 and 20'
1114                         );
1115                     }
1116                     $params['count'] = $value;
1117                     break;
1118                 case 'page':
1119                     $params['page'] = (int) $value;
1120                     break;
1121                 case 'include_entities':
1122                     $params['include_entities'] = (bool) $value;
1123                     break;
1124                 default:
1125                     break;
1126             }
1127         }
1128         $response = $this->get($path, $params);
1129         return new Zend_Service_Twitter_Response($response);
1130     }
1131 
1132 
1133     /**
1134      * Show extended information on a user
1135      *
1136      * @param  int|string $id User ID or name
1137      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
1138      * @throws Exception\DomainException if unable to decode JSON payload
1139      * @return Zend_Service_Twitter_Response
1140      */
1141     public function usersShow($id)
1142     {
1143         $this->init();
1144         $path     = 'users/show';
1145         $params   = $this->createUserParameter($id, array());
1146         $response = $this->get($path, $params);
1147         return new Zend_Service_Twitter_Response($response);
1148     }
1149 
1150     /**
1151      * Initialize HTTP authentication
1152      *
1153      * @return void
1154      * @throws Exception\DomainException if unauthorised
1155      */
1156     protected function init()
1157     {
1158         if (!$this->isAuthorised() && $this->getUsername() !== null) {
1159             // require_once 'Zend/Service/Twitter/Exception.php';
1160             throw new Zend_Service_Twitter_Exception(
1161                 'Twitter session is unauthorised. You need to initialize '
1162                 . __CLASS__ . ' with an OAuth Access Token or use '
1163                 . 'its OAuth functionality to obtain an Access Token before '
1164                 . 'attempting any API actions that require authorisation'
1165             );
1166         }
1167         $client = $this->getHttpClient();
1168         $client->resetParameters();
1169         if (null === $this->cookieJar) {
1170             $cookieJar = $client->getCookieJar();
1171             if (null === $cookieJar) {
1172                 $cookieJar = new Zend_Http_CookieJar();
1173             }
1174             $this->cookieJar = $cookieJar;
1175             $this->cookieJar->reset();
1176         } else {
1177             $client->setCookieJar($this->cookieJar);
1178         }
1179     }
1180 
1181     /**
1182      * Protected function to validate that the integer is valid or return a 0
1183      *
1184      * @param  $int
1185      * @throws Zend_Http_Client_Exception if HTTP request fails or times out
1186      * @return integer
1187      */
1188     protected function validInteger($int)
1189     {
1190         if (preg_match("/(\d+)/", $int)) {
1191             return $int;
1192         }
1193         return 0;
1194     }
1195 
1196     /**
1197      * Validate a screen name using Twitter rules
1198      *
1199      * @param string $name
1200      * @return string
1201      * @throws Exception\InvalidArgumentException
1202      */
1203     protected function validateScreenName($name)
1204     {
1205         if (!preg_match('/^[a-zA-Z0-9_]{0,20}$/', $name)) {
1206             // require_once 'Zend/Service/Twitter/Exception.php';
1207             throw new Zend_Service_Twitter_Exception(
1208                 'Screen name, "' . $name
1209                 . '" should only contain alphanumeric characters and'
1210                 . ' underscores, and not exceed 15 characters.');
1211         }
1212         return $name;
1213     }
1214 
1215     /**
1216      * Call a remote REST web service URI
1217      *
1218      * @param  string $path The path to append to the URI
1219      * @param  Zend_Http_Client $client
1220      * @throws Zend_Http_Client_Exception
1221      * @return void
1222      */
1223     protected function prepare($path, Zend_Http_Client $client)
1224     {
1225         $client->setUri(self::API_BASE_URI . $path . '.json');
1226 
1227         /**
1228          * Do this each time to ensure oauth calls do not inject new params
1229          */
1230         $client->resetParameters();
1231     }
1232 
1233     /**
1234      * Performs an HTTP GET request to the $path.
1235      *
1236      * @param string $path
1237      * @param array  $query Array of GET parameters
1238      * @throws Zend_Http_Client_Exception
1239      * @return Zend_Http_Response
1240      */
1241     protected function get($path, array $query = array())
1242     {
1243         $client = $this->getHttpClient();
1244         $this->prepare($path, $client);
1245         $client->setParameterGet($query);
1246         $response = $client->request(Zend_Http_Client::GET);
1247         return $response;
1248     }
1249 
1250     /**
1251      * Performs an HTTP POST request to $path.
1252      *
1253      * @param string $path
1254      * @param mixed $data Raw data to send
1255      * @throws Zend_Http_Client_Exception
1256      * @return Zend_Http_Response
1257      */
1258     protected function post($path, $data = null)
1259     {
1260         $client = $this->getHttpClient();
1261         $this->prepare($path, $client);
1262         $response = $this->performPost(Zend_Http_Client::POST, $data, $client);
1263         return $response;
1264     }
1265 
1266     /**
1267      * Perform a POST or PUT
1268      *
1269      * Performs a POST or PUT request. Any data provided is set in the HTTP
1270      * client. String data is pushed in as raw POST data; array or object data
1271      * is pushed in as POST parameters.
1272      *
1273      * @param mixed $method
1274      * @param mixed $data
1275      * @return Zend_Http_Response
1276      */
1277     protected function performPost($method, $data, Zend_Http_Client $client)
1278     {
1279         if (is_string($data)) {
1280             $client->setRawData($data);
1281         } elseif (is_array($data) || is_object($data)) {
1282             $client->setParameterPost((array) $data);
1283         }
1284         return $client->request($method);
1285     }
1286 
1287     /**
1288      * Create a parameter representing the user
1289      *
1290      * Determines if $id is an integer, and, if so, sets the "user_id" parameter.
1291      * If not, assumes the $id is the "screen_name".
1292      *
1293      * @param  int|string $id
1294      * @param  array $params
1295      * @return array
1296      */
1297     protected function createUserParameter($id, array $params)
1298     {
1299         if ($this->validInteger($id)) {
1300             $params['user_id'] = $id;
1301             return $params;
1302         }
1303 
1304         $params['screen_name'] = $this->validateScreenName($id);
1305         return $params;
1306     }
1307 }