diff --git a/twitter.inc b/twitter.inc index 52f926c..fd5d9fd 100644 --- a/twitter.inc +++ b/twitter.inc @@ -292,6 +292,21 @@ function twitter_statuses_oembed($tweet_id) { } /** + * Upload media (image) to twitter + * + * @param $twitter_account + * object with a Twitter account. + * @param $media + * raw binary file content being uploaded. + * @return + * array response from Twitter API. + */ +function twitter_upload_media($twitter_account, $filename, $filemime, $media) { + $twitter = twitter_connect($twitter_account); + return $twitter->media_upload($filename, $filemime, $media); +} + +/** * Fetches a user's timeline. */ function twitter_fetch_user_timeline($id) { diff --git a/twitter.lib.php b/twitter.lib.php index a37a972..a957db5 100644 --- a/twitter.lib.php +++ b/twitter.lib.php @@ -107,8 +107,14 @@ class Twitter { /** * Performs an authenticated request. */ - public function auth_request($url, $params = array(), $method = 'GET') { - $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $params); + public function auth_request($url, $params = array(), $method = 'GET', $multipart = FALSE) { + if ($multipart) { + // don't use status params because multipart form data shouldn't get signed + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, array()); + } + else { + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $params); + } $request->sign_request($this->signature_method, $this->consumer, $this->token); try { @@ -116,7 +122,8 @@ class Twitter { case 'GET': return $this->request($request->to_url()); case 'POST': - return $this->request($request->get_normalized_http_url(), $request->get_parameters(), 'POST'); + $params = array_merge($params, $request->get_parameters()); + return $this->request($request->get_normalized_http_url(), $params, 'POST', $multipart); } } catch (TwitterException $e) { @@ -130,22 +137,45 @@ class Twitter { * * @throws TwitterException */ - protected function request($url, $params = array(), $method = 'GET') { + protected function request($url, $params = array(), $method = 'GET', $multipart = FALSE) { $data = ''; + if ($multipart) { + $boundary = md5(uniqid()); + } + + $headers = array(); + + $headers['Authorization'] = 'OAuth'; + if ($multipart) { + $headers['Content-type'] = "multipart/form-data; boundary=\"$boundary\""; + } + else { + $headers['Content-type'] = 'application/x-www-form-urlencoded'; + } + if (count($params) > 0) { if ($method == 'GET') { $url .= '?'. http_build_query($params, '', '&'); } else { - $data = http_build_query($params, '', '&'); + if (!$multipart) { + $data = http_build_query($params, '', '&'); + } + else { + $oauth_params = array(); + foreach ($params as $key => $val) { + if (stripos($key, 'oauth_') === 0) { + $oauth_params[] = $key .'="' . urlencode($val) . '"'; + unset($params[$key]); + } + } + // for multipart, oauth params get tagged onto the authorization header + $headers['Authorization'] .= ' ' . implode(", " ,$oauth_params); + $data = $this->multipart_encode($boundary, $params); + } } } - $headers = array(); - - $headers['Authorization'] = 'Oauth'; - $headers['Content-type'] = 'application/x-www-form-urlencoded'; - $response = $this->doRequest($url, $headers, $method, $data); if (!isset($response->error)) { return $response->data; @@ -176,6 +206,18 @@ class Twitter { } /** + * Multipart encode the request + */ + protected function multipart_encode($boundary, $params) { + $output = "--$boundary\r\n"; + $output .= "Content-Disposition: form-data; name=\"media\"; filename=\"" . $params['filename'] . "\"\r\n"; + $output .= "Content-Type: " . $params['filemime'] . "\r\n\r\n"; + $output .= $params['media'] . "\r\n"; + $output .= "--$boundary--"; + return $output; + } + + /** * Actually performs a request. * * This method can be easily overriden through inheritance. @@ -214,8 +256,14 @@ class Twitter { * @return * The complete path to the endpoint. */ - protected function create_url($path, $format = '.json') { - $url = variable_get('twitter_api', TWITTER_API) .'/1.1/'. $path . $format; + protected function create_url($path, $multipart = FALSE, $format = '.json') { + if ($multipart) { + $url = variable_get('twitter_upload', TWITTER_UPLOAD); + } + else { + $url = variable_get('twitter_api', TWITTER_API); + } + $url .= '/1.1/'. $path . $format; return $url; } @@ -399,26 +447,6 @@ class Twitter { } /** - * Creates a Tweet with a picture attached. - * - * @param string $status - * The text of the status update (the tweet). - * @param array $media - * An array of physical paths of images. - * @param array $params - * an array of parameters. - * - * @see https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media - */ - public function statuses_update_with_media($status, $media, $params = array()) { - $params['status'] = $status; - $params['media[]'] = '@{' . implode(',', $media) . '}'; - $values = $this->call('statuses/statuses/update_with_media', $params, 'POST'); - // @TODO support media at TwitterStatus class. - return new TwitterStatus($values); - } - - /** * Returns information allowing the creation of an embedded representation of * a Tweet on third party sites. * @@ -440,6 +468,25 @@ class Twitter { } /********************************************//** + * Media + ***********************************************/ + /** + * Upload media (images) to use in a Tweet or Twitter-hosted Card. + * + * @param string $media + * The raw binary file content being uploaded. + * + * @see https://dev.twitter.com/rest/reference/post/media/upload + */ + public function media_upload($filename, $filemime, $media) { + $params = array(); + $params['filename'] = $filename; + $params['filemime'] = $filemime; + $params['media'] = $media; + return $this->call('media/upload', $params, 'POST', TRUE); + } + + /********************************************//** * Search ***********************************************/ /** @@ -1241,11 +1288,11 @@ class Twitter { /** * Calls a Twitter API endpoint. */ - public function call($path, $params = array(), $method = 'GET') { - $url = $this->create_url($path); + public function call($path, $params = array(), $method = 'GET', $multipart = FALSE) { + $url = $this->create_url($path, $multipart); try { - $response = $this->auth_request($url, $params, $method); + $response = $this->auth_request($url, $params, $method, $multipart); } catch (TwitterException $e) { watchdog('twitter', '!message', array('!message' => $e->__toString()), WATCHDOG_ERROR); diff --git a/twitter.module b/twitter.module index 4080979..5790dd9 100644 --- a/twitter.module +++ b/twitter.module @@ -6,6 +6,7 @@ define ('TWITTER_HOST', 'https://twitter.com'); define ('TWITTER_API', 'https://api.twitter.com'); +define ('TWITTER_UPLOAD', 'https://upload.twitter.com'); define ('TWITTER_SEARCH', 'https://twitter.com'); define ('TWITTER_TINYURL', 'http://tinyurl.com'); define ('TWITTER_OAUTH_CALLBACK_URL', 'twitter/oauth'); diff --git a/twitter.pages.inc b/twitter.pages.inc index 5465b7c..ff447ee 100644 --- a/twitter.pages.inc +++ b/twitter.pages.inc @@ -76,6 +76,11 @@ function twitter_admin_form($form, &$form_state) { '#title' => t('Twitter API'), '#default_value' => variable_get('twitter_api', TWITTER_API), ); + $form['twitter']['twitter_upload'] = array( + '#type' => 'textfield', + '#title' => t('Twitter Upload'), + '#default_value' => variable_get('twitter_upload', TWITTER_UPLOAD), + ); $form['twitter']['twitter_search'] = array( '#type' => 'textfield', '#title' => t('Twitter search'),