<?php /** * An extension of the Codebird class to use Wordpress' HTTP API instead of * cURL. * * @version 1.1.2 */ class WP_Codebird extends \Codebird\Codebird { /** * The current singleton instance */ private static $_instance = null; /** * The file formats extensions twitter supports * @var array */ protected $_supported_media_files_extensions = array( 'gif', 'jpg', 'jpeg', 'png' ); /** * Returns singleton class instance * Always use this method unless you're working with multiple authenticated * users at once. * * This method had to be overloaded because self was used * in the references instead of get_called_class. So the method was always * returning instances of Codebird instead of WP_Codebird. * * @return Codebird The instance */ public static function getInstance() { if ( self::$_instance == null ) { self::$_instance = new self; } return self::$_instance; } /** * Overload magic __call() to transparently intercept Exceptions * * Most exceptions encountered in production are API timeouts - this will * transparently handle these Exceptions to prevent fatal errors */ public function __call( $function, $arguments ) { try { return parent::__call( $function, $arguments ); } catch ( Exception $e ) { return array(); } } /** * Calls the API using Wordpress' HTTP API. * * @since 0.1.0 * @see Codebird::_callApi * * @param string $httpmethod The HTTP method to use for making the request * @param string $method The API method to call * @param string $method_template The templated API method to call * @param array optional $params The parameters to send along * @param bool optional $multipart Whether to use multipart/form-data * * @return mixed The API reply, encoded in the set return_format. */ protected function _callApi( $httpmethod, $method, $method_template, $params = array(), $multipart = false, $app_only_auth = false ) { $url = $this->_getEndpoint( $method, $method_template ); $url_with_params = null; $authorization = null; $remote_params = array( 'method' => $httpmethod, 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, 'headers' => array(), 'body' => null, 'cookies' => array(), 'sslverify' => false ); if ( 'GET' == $httpmethod ) { $url_with_params = $url; if ( count( $params ) > 0 ) { $url_with_params .= '?' . http_build_query( $params ); } $authorization = $this->_sign( $httpmethod, $url, $params ); $url = $url_with_params; } else { if ( $multipart ) { $authorization = $this->_sign( $httpmethod, $url, array() ); $params = $this->_buildMultipart( $method_template, $params ); // Add the boundaries $first_newline = strpos( $params, "\r\n" ); $multipart_boundary = substr( $params, 2, $first_newline - 2 ); $remote_params['headers']['Content-Length'] = strlen( $params ); $remote_params['headers']['Content-Type'] = 'multipart/form-data; boundary=' . $multipart_boundary; } else { $authorization = $this->_sign( $httpmethod, $url, $params ); $params = http_build_query( $params ); } $remote_params['body'] = $params; } if ( $app_only_auth ) { if ( null == self::$_oauth_consumer_key ) { throw new Exception( 'To make an app-only auth API request, the consumer key must be set' ); } // automatically fetch bearer token, if necessary if ( null == self::$_oauth_bearer_token ) { $this->oauth2_token(); } $authorization = 'Authorization: Bearer ' . self::$_oauth_bearer_token; } // Codebird::_sign() adds Authorization: to $authorization, but the WP HTTP API needs it separate $authorization = trim( str_replace( 'Authorization:', '', $authorization ) ); if ( $authorization ) { $remote_params['headers']['Authorization'] = $authorization; $remote_params['headers']['Expect'] = ''; } if ( 'GET' == $httpmethod ) { $reply = wp_remote_get( $url, $remote_params ); } else { $reply = wp_remote_post( $url, $remote_params ); } if ( isset( $reply ) ) { if ( is_wp_error( $reply ) ) { throw new Exception( $reply->get_error_message() ); } else { $httpstatus = $reply['response']['code']; $reply = $this->_parseApiReply( $method_template, $reply ); if ( $this->_return_format == CODEBIRD_RETURNFORMAT_OBJECT ) { $reply->httpstatus = $httpstatus; } else { $reply['httpstatus'] = $httpstatus; } } } else { throw new Exception( 'A reply was never generated. Some has gone horribly awry.' ); } return $reply; } /** * Gets the OAuth bearer token * * Overridden to use the WordPress HTTP API * * @return string The OAuth bearer token */ public function oauth2_token() { if ( null == self::$_oauth_consumer_key ) { throw new Exception( 'To obtain a bearer token, the consumer key must be set.' ); } $post_fields = array( 'grant_type' => 'client_credentials' ); $url = self::$_endpoint_oauth . 'oauth2/token'; $headers = array( 'Authorization' => 'Basic ' . base64_encode( self::$_oauth_consumer_key . ':' . self::$_oauth_consumer_secret ), 'Expect' => '' ); $remote_params = array( 'method' => 'POST', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, 'headers' => $headers, 'body' => $post_fields, 'cookies' => array(), 'sslverify' => false ); $reply = wp_remote_post( $url, $remote_params ); $httpstatus = wp_remote_retrieve_response_code( $reply ); $reply = $this->_parseApiReply( 'oauth2/token', $reply ); if ( CODEBIRD_RETURNFORMAT_OBJECT == $this->_return_format ) { $reply->httpstatus = $httpstatus; if ( 200 == $httpstatus ) { self::setBearerToken( $reply->access_token ); } } else { $reply['httpstatus'] = $httpstatus; if ( 200 == $httpstatus ) { self::setBearerToken( $reply['access_token'] ); } } return $reply; } /** * Parses the API reply to encode it in the set return_format. * * @since 0.1.0 * @see Codebird::_parseApiReply * * @param string $method The method that has been called * @param string $reply The actual reply, JSON-encoded or URL-encoded * * @return array|object The parsed reply */ protected function _parseApiReply( $method, $reply ) { // split headers and body $http_response = $reply; $headers = $http_response['headers']; $reply = ''; if ( isset( $http_response['body'] ) ) { $reply = $http_response['body']; } $need_array = $this->_return_format == CODEBIRD_RETURNFORMAT_ARRAY; if ( $reply == '[]' ) { return $need_array ? array() : new stdClass; } $parsed = array(); if ( $method == 'users/profile_image/:screen_name' ) { // this method returns a 302 redirect, we need to extract the URL if ( isset( $headers['Location'] ) ) { $parsed = array( 'profile_image_url_https' => $headers['Location'] ); } } elseif ( ! $parsed = json_decode( $reply, $need_array ) ) { if ( $reply ) { $reply = explode( '&', $reply ); foreach ( $reply as $element ) { if ( stristr( $element, '=' ) ) { list( $key, $value ) = explode( '=', $element ); $parsed[$key] = $value; } else { $parsed['message'] = $element; } } } } if ( ! $need_array ) { $parsed = ( object ) $parsed; } return $parsed; } /** * Detect filenames in upload parameters, * build multipart request from upload params * * @param string $method The API method to call * @param array $params The parameters to send along * * @return void */ protected function _buildMultipart( $method, $params ) { // well, files will only work in multipart methods if ( ! $this->_detectMultipart( $method ) ) { return; } // only check specific parameters $possible_files = array( // Tweets 'statuses/update_with_media' => 'media[]', // Accounts 'account/update_profile_background_image' => 'image', 'account/update_profile_image' => 'image', 'account/update_profile_banner' => 'banner' ); // method might have files? if ( ! in_array( $method, array_keys( $possible_files ) ) ) { return; } $possible_files = explode( ' ', $possible_files[$method] ); $multipart_border = '--------------------' . $this->_nonce(); $multipart_request = ''; foreach ( $params as $key => $value ) { // is it an array? if ( is_array( $value ) ) { _doing_it_wrong( '_buildMultiPart()', 'Using URL-encoded parameters is not supported for uploading media.', '3.7.1' ); continue; } $multipart_request .= '--' . $multipart_border . "\r\n" . 'Content-Disposition: form-data; name="' . $key . '"'; // check for filenames if ( in_array( $key, $possible_files ) ) { $data = false; if ( filter_var( $value, FILTER_VALIDATE_URL ) ) { $filename = basename( parse_url( $value, PHP_URL_PATH ) ); if ( $this->is_supported_media_file_extension( $filename ) ) { $data = getimagesize( $value ); } } elseif ( file_exists( $value ) && is_readable( $value ) && $this->is_supported_media_file_extension( basename( $value ) ) ) { $data = getimagesize( $value ); } // is it a supported image format? if ( $data && in_array( $data[2], $this->_supported_media_files ) ) { // try to read the file $data = file_get_contents( $value ); if ( strlen( $data ) == 0 ) { continue; } $value = $data; } else { continue; } } $multipart_request .= "\r\n\r\n" . $value . "\r\n"; } $multipart_request .= '--' . $multipart_border . '--'; return $multipart_request; } private function is_supported_media_file_extension( $filename ) { $extension = pathinfo( $filename, PATHINFO_EXTENSION ); return in_array( strtolower( $extension ), $this->_supported_media_files_extensions ); } }