diff --git a/twitter_pull.class.inc b/twitter_pull.class.inc index 623fdc3..234a841 100644 --- a/twitter_pull.class.inc +++ b/twitter_pull.class.inc @@ -7,9 +7,12 @@ class twitter_puller { - var $twitkey; - var $num_items; - var $tweets; + private $twitkey; + private $num_items; + private $consumer_key; + private $consumer_secret; + private $oauth_token; + private $oauth_token_secret; /** * @param $twitkey @@ -17,16 +20,19 @@ class twitter_puller { * @param $num_items * maximum number of tweets to pull from Twitter. */ - function __construct($twitkey, $num_items) { + public function __construct($twitkey, $num_items) { $this->twitkey = $twitkey; $this->num_items = $num_items; + $this->consumer_key = variable_get('consumer_key', ''); + $this->consumer_secret = variable_get('consumer_secret', ''); + $this->oauth_token = variable_get('oauth_token', ''); + $this->oauth_token_secret = variable_get('oauth_token_secret', ''); $this->check_arguments(); - } - function check_arguments() { + private function check_arguments() { if (empty($this->twitkey) || drupal_strlen($this->twitkey) < 2) { throw new Exception(t('Twitter key may not be empty.')); @@ -37,108 +43,132 @@ class twitter_puller { throw new Exception(t('Number of Twitter items to pull must be a positive integer less than or equal to 200.')); } + if (empty($this->consumer_key) || !drupal_strlen($this->consumer_key)) { + throw new Exception(t('Twitter consumer key is empty.')); + } + + if (empty($this->consumer_secret) || !drupal_strlen($this->consumer_secret)) { + throw new Exception(t('Twitter consumer secret is empty.')); + } + + if (empty($this->oauth_token) || !drupal_strlen($this->oauth_token)) { + throw new Exception(t('Twitter access token is empty.')); + } + + if (empty($this->oauth_token_secret) || !drupal_strlen($this->oauth_token_secret)) { + throw new Exception(t('Twitter access token secret is empty.')); + } + } - function get_items() { + public function get_items() { + // Get twitkey statistics. $prefix = drupal_substr($this->twitkey, 0, 1); $slash = strpos($this->twitkey, '/', 1); $num = intval($this->num_items); - - // lists have the format @username/listname - if ($prefix == '@' && $slash !== FALSE) { - $username = drupal_substr($this->twitkey, 1, $slash - 1); - $listname = drupal_substr($this->twitkey, $slash + 1); - $url = 'http://api.twitter.com/1/' . urlencode($username) . '/lists/' . urlencode($listname) . '/statuses.json?per_page=' . $num; - } - // if the first character is @, then consider the key a username - elseif ($prefix == "@") { - $key = drupal_substr($this->twitkey, 1); - $url = 'http://api.twitter.com/1/statuses/user_timeline.json?screen_name='. urlencode($key) .'&include_rts=true&count='. $num; - } - // if the first character is ~, then consider the key a favorites feed - elseif ($prefix == "~") { - $key = drupal_substr($this->twitkey, 1); - $url = 'http://api.twitter.com/1/favorites/'.urlencode($key).'.json?count='.$num; - } - // otherwise, use the key as a search term - else { - if ($num > 100) { - $num = 100; - } - $url = 'http://search.twitter.com/search.json?q=' . urlencode($this->twitkey) . '&rpp=' . $num; - } - - $ret = drupal_http_request($url, array('timeout' => 2)); - - if ($ret->code < 200 || $ret->code > 399) { - $errmsg = t('An unknown error occurred.'); - if (isset($ret->error) && !empty($ret->error)) { - $errmsg = check_plain($ret->error); - } - elseif (isset($ret->data) && !empty($ret->data)) { - $errdata = json_decode($ret->data); - if (isset($errdata->error) && !empty($errdata->error)) { - $errmsg = check_plain($errmsg->error); + $key = drupal_substr($this->twitkey, 1); + + // Start building the parameters for the twitter api. + $params = array( + 'count' => $num, + ); + + // Determin the type of request. + // Set up the path and params according to the type of request. + switch ($prefix) { + case "@": + if ($slash === FALSE) { + // Just a normal user timeline. + $path = "statuses/user_timeline.json"; + $params['screen_name'] = $key; + } + else { + // Since we have at least one slash, we are going to get a list. + $path = "lists/statuses.json"; + $params['owner_screen_name'] = drupal_substr($this->twitkey, 1, $slash - 1); + $params['slug'] = drupal_substr($this->twitkey, $slash + 1); } - } - if ($ret->code == 400) { - $errmsg .= ' ' . t('This site may be subject to rate limiting. For more information, see: http://apiwiki.twitter.com/Rate-limiting'); - } - throw new Exception(t('Could not retrieve data from Twitter.') . ' ' . $errmsg); + break; + + case "~": + // Looking for favorites. + $path = "favorites/list.json"; + $params['screen_name'] = $key; + break; + + default: + // Default to a normal search. + $path = "search/tweets.json"; + $params['count'] = $num > 100 ? 100 : $num; + $params['q'] = $this->twitkey; + break; } - $items = json_decode($ret->data); - $this->parse_items($items); + $results = $this->request($path, $params); + $results = drupal_json_decode($results); + return $this->parse_items($results); } - function parse_items($items) { - $tweets = array(); + private function request($path, $params) { + + $consumer_key = $this->consumer_key; + $consumer_secret = $this->consumer_secret; + $oauth_token = $this->oauth_token; + $oauth_token_secret = $this->oauth_token_secret; + $url = 'https://api.twitter.com/1.1/' . $path; - //-- If search response then items are one level lower. - if (isset($items->results) && is_array($items->results)) { - $items = $items->results; + $signature_method = new OAuthSignatureMethod_HMAC_SHA1(); + $consumer = new OAuthConsumer($consumer_key, $consumer_secret); + $token = new OAuthToken($oauth_token, $oauth_token_secret); + + $request = OAuthRequest::from_consumer_and_token($consumer, $token, 'GET', $url, $params); + $request->sign_request($signature_method, $consumer, $token); + + return $this->do_request($request->to_url()); + } + + private function do_request($url) { + + $headers = array(); + + $headers['Authorization'] = 'Oauth'; + $headers['Content-type'] = 'application/x-www-form-urlencoded'; + + $response = drupal_http_request($url, array('headers' => $headers, 'method' => 'GET')); + + if (!isset($response->error)) { + return $response->data; + } + else { + $error = $response->error; + throw new Exception(t('Twitter request failed: @error.', array('@error' => $response->error))); } + } - if (is_array($items)) { - $items = array_slice($items, 0, $this->num_items); - foreach ($items as $item) { - $obj = new stdClass(); - - if (isset($item->retweeted_status)) { - $obj->id = check_plain($item->retweeted_status->id_str); - $obj->username = (isset($item->retweeted_status->user) && !empty($item->retweeted_status->user->screen_name)) ? $item->retweeted_status->user->screen_name : $item->retweeted_status->from_user; - $obj->username = check_plain($obj->username); - //get the user photo for the retweet - $obj->userphoto = (isset($item->retweeted_status->user) && !empty($item->retweeted_status->user->profile_image_url)) ? $item->retweeted_status->user->profile_image_url : $item->retweeted_status->profile_image_url; - $obj->userphoto = check_plain($obj->userphoto); - $obj->userphoto_https = (isset($item->retweeted_status->user) && !empty($item->retweeted_status->user->profile_image_url_https)) ? $item->retweeted_status->user->profile_image_url_https : $item->retweeted_status->profile_image_url_https; - $obj->userphoto_https = check_plain($obj->userphoto_https); - - $obj->text = filter_xss($item->retweeted_status->text); - //-- Convert date to unix timestamp so themer can easily work with it. - $obj->timestamp = strtotime($item->retweeted_status->created_at); - } - else { - $obj->id = check_plain($item->id_str); - $obj->username = (isset($item->user) && !empty($item->user->screen_name)) ? $item->user->screen_name : $item->from_user; - $obj->username = check_plain($obj->username); - //retrieve the user photo - $obj->userphoto = (isset($item->user) && !empty($item->user->profile_image_url)) ? $item->user->profile_image_url : $item->profile_image_url; - $obj->userphoto = check_plain($obj->userphoto); - $obj->userphoto_https = (isset($item->user) && !empty($item->user->profile_image_url_https)) ? $item->user->profile_image_url_https : $item->profile_image_url_https; - $obj->userphoto_https = check_plain($obj->userphoto_https); - - $obj->text = filter_xss($item->text); - //-- Convert date to unix timestamp so themer can easily work with it. - $obj->timestamp = strtotime($item->created_at); - } - $tweets[] = $obj; - } + private function parse_items($results) { + + $this->tweets = array(); + + // Search results return metadata and the results in a sub-array + // We need to parse the actual result array. + $results = isset($results['statuses']) ? $results['statuses'] : $results; + + foreach ($results as $status) { + $tweet = new stdClass(); + $tweet->id = $status['id']; + $tweet->text = $status['text']; + $tweet->timestamp = strtotime($status['created_at']); + $tweet->username = $status['user']['name']; + $tweet->userphoto = $status['user']['profile_image_url']; + $tweet->userphoto_https = $status['user']['profile_image_url_https']; + $tweet->time_ago = t('!time ago.', array('!time' => format_interval(time() - $tweet->timestamp))); + + $this->tweets[] = $tweet; } - $this->tweets = $tweets; + return $this->tweets; } } diff --git a/twitter_pull.info b/twitter_pull.info index 9f348ab..fea3a18 100644 --- a/twitter_pull.info +++ b/twitter_pull.info @@ -6,10 +6,11 @@ files[] = twitter_pull.module files[] = twitter_pull.install files[] = plugins/twitter_pull_box.inc files[] = twitter_pull.class.inc +dependencies[] = "oauth_common" -; Information added by drupal.org packaging script on 2012-12-11 -version = "7.x-1.0-rc4+1-dev" +; Information added by drupal.org packaging script on 2011-11-24 +version = "7.x-1.x-dev" core = "7.x" project = "twitter_pull" -datestamp = "1355190015" +datestamp = "1322095471" diff --git a/twitter_pull.module b/twitter_pull.module index 923169f..f23a5f6 100644 --- a/twitter_pull.module +++ b/twitter_pull.module @@ -10,6 +10,73 @@ define ('TWITTER_PULL_CACHE_LENGTH', 20); //-- cache for 20 minutes define ('TWITTER_PULL_EMPTY_MESSAGE', 'No Tweets'); define ('TWITTER_PULL_CACHE_TABLE', 'cache_pulled_tweets'); +/** + * Implements hook_menu(). + */ +function twitter_pull_menu() { + $items = array(); + + $items['admin/config/services/twitter-pull-config'] = array( + 'title' => 'Twitter pull', + 'description' => 'Configuration settings for the twitter pull module', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('twitter_pull_admin'), + 'access arguments' => array('administer twitter pull'), + 'type' => MENU_NORMAL_ITEM, + ); + + return $items; +} + +/** + * Implements hook_permission(). + */ +function twitter_pull_permission() { + return array( + 'administer twitter pull' => array( + 'title' => t('Administer twitter pull'), + 'description' => t('Administer twitter pull configurations.'), + ), + ); +} + +/** + * Settings form for the twitter pull configurations + */ +function twitter_pull_admin() { + $form = array(); + + $form['consumer_key'] = array( + '#type' => 'textfield', + '#title' => t('Consumer key'), + '#default_value' => variable_get('consumer_key', ''), + '#description' => t("Consumer key from your twitter application."), + ); + + $form['consumer_secret'] = array( + '#type' => 'textfield', + '#title' => t('Consumer_secret'), + '#default_value' => variable_get('consumer_secret', ''), + '#description' => t("Consumer secret from your twitter application. Remember this should not be shared."), + ); + + $form['oauth_token'] = array( + '#type' => 'textfield', + '#title' => t('Access token'), + '#default_value' => variable_get('oauth_token', ''), + '#description' => t("Access token from your twitter application."), + ); + + $form['oauth_token_secret'] = array( + '#type' => 'textfield', + '#title' => t('Access token secret'), + '#default_value' => variable_get('oauth_token_secret', ''), + '#description' => t("Access token secret from your twitter application. Remember this should not be shared."), + ); + + return system_settings_form($form); +} + function twitter_pull_num_items() { return variable_get('twitter_pull_num_items', TWITTER_PULL_NUM_ITEMS); } @@ -23,25 +90,18 @@ function twitter_pull_empty_message() { } /** - * Implements hook_menu(). + * Implements hook_flush_caches(). */ -function twitter_pull_menu() { - $items = array(); - - $items['twitter_pull_lazy/%'] = array( - 'page callback' => 'twitter_pull_lazy', - 'page arguments' => array(1), - 'access arguments' => array('access content'), - ); - - return $items; +function twitter_pull_flush_caches() { + return array(TWITTER_PULL_CACHE_TABLE); } /** - * Implements hook_flush_caches(). + * Implements hook_init(). */ -function twitter_pull_flush_caches() { - return array(TWITTER_PULL_CACHE_TABLE); +function twitter_pull_init() { + $css_path = drupal_get_path('module', 'twitter_pull') . '/twitter-pull-listing.css'; + drupal_add_css($css_path); } /** @@ -52,25 +112,12 @@ function twitter_pull_flush_caches() { function twitter_pull_theme() { return array( 'twitter_pull_listing' => array( - 'variables' => array('tweets' => NULL, 'twitkey' => NULL, 'title' => NULL, 'lazy_load' => NULL), + 'variables' => array('tweets' => NULL, 'twitkey' => NULL, 'title' => NULL), 'template' => 'twitter-pull-listing', ), ); } -function twitter_pull_preprocess(&$variables, $hook) { - switch($hook) { - case 'twitter_pull_listing': - if (!empty($variables['tweets']) && is_array($variables['tweets'])) { - foreach ($variables['tweets'] as $key => $tweet) { - $tweet->time_ago = t('!time ago.', array('!time' => format_interval(time() - $tweet->timestamp))); - $variables['tweets'][$key] = $tweet; - } - } - break; - } -} - /** * Retrieves appropriate tweets (by username, hashkey or search term) * and passes over to the theming function with $themekey key passing @@ -88,35 +135,17 @@ function twitter_pull_preprocess(&$variables, $hook) { * Number of tweets to retrieve from Twitter. Can't be more than 200. * @param $themekey * Theme key name to use for theming the output of Twitter API. - * @param $lazy_load - * Use javascript to retrieve the twitter results once the page is loaded. */ -function twitter_pull_render($twitkey, $title = NULL, $num_items = NULL, $themekey = NULL, $lazy_load = FALSE) { - drupal_add_css(drupal_get_path('module', 'twitter_pull') . '/twitter-pull-listing.css'); - - //-- Set the lazy load id. Encode the twitkey and title to make sure the they don't contain dashes. - $lazy_id = rtrim(base64_encode($twitkey) .'-'. base64_encode($title) .'-'. (int)$num_items . '-' . $themekey, '-'); - +function twitter_pull_render($twitkey, $title = NULL, $num_items = NULL, $themekey = NULL) { //-- Set defaults if empty arguments were passed $title = (empty($title) && $title != FALSE ) ? t('Related Tweets') : $title; $themekey = empty($themekey) ? 'twitter_pull_listing' : $themekey; $num_items = empty($num_items) ? twitter_pull_num_items() : $num_items; - if (!$lazy_load) { - $tweets = twitter_pull_retrieve($twitkey, $num_items); - } - else { - $tweets = NULL; - $uri = url('twitter_pull_lazy/' . $lazy_id); - $id = uniqid('twitter-pull-lazy-'); - - $lazy_load = '
' . t('Loading...') . '
'; - drupal_add_js('jQuery(document).ready(function () { jQuery.get("' . $uri . '", function(data) { jQuery("#'. $id . '").html(data).removeClass("throbber"); }); });', 'inline'); - } - + $tweets = twitter_pull_retrieve($twitkey, $num_items); module_invoke_all('twitter_pull_modify', $tweets); - $ret = theme($themekey, array('tweets' => $tweets, 'twitkey' => $twitkey, 'title' => $title, 'lazy_load' => $lazy_load)); + $ret = theme($themekey, array('tweets' => $tweets, 'twitkey' => $twitkey, 'title' => $title)); if (empty($ret) && !empty($tweets)) { $errmsg = t("Non-empty list of tweets returned blank space after applying theme function. Most probably you are passing invalid/unregistered theme key or tpl file corresponding to the theme key does not yet exist. Please fix the problem."); @@ -137,8 +166,6 @@ function twitter_pull_render($twitkey, $title = NULL, $num_items = NULL, $themek * Number of tweets to retrieve from Twitter. Can't be more than 200. */ function twitter_pull_retrieve($twitkey, $num_items = NULL) { - global $is_https; - // If $num_items is not set, use the default value. // This value is checked more rigorously in twitter_puller->check_arguments(). $num_items = (intval($num_items) > 0) ? intval($num_items) : twitter_pull_num_items(); @@ -160,12 +187,7 @@ function twitter_pull_retrieve($twitkey, $num_items = NULL) { } catch (Exception $e) { watchdog('Twitter Pull', $e->getMessage(), array(), WATCHDOG_WARNING); - if (!empty($cache) && !empty($cache->data)) { - return $cache->data; - } - else { - return twitter_pull_empty_message(); - } + return twitter_pull_empty_message(); } if (!empty($tweets) && is_array($tweets)) { @@ -174,14 +196,6 @@ function twitter_pull_retrieve($twitkey, $num_items = NULL) { } } - // If we have tweets and are viewing a secure site, we want to set the url - // to the userphoto to use the secure image to avoid insecure errors. - if (!empty($tweets) && is_array($tweets) && $is_https) { - foreach ($tweets as $i => $tweet) { - $tweets[$i]->userphoto = $tweet->userphoto_https; - } - } - return $tweets; } @@ -194,17 +208,17 @@ function twitter_pull_add_links($text) { $text = preg_replace($pattern, $repl, $text); $pattern = '#@(\w+)#ims'; - $repl = '@$1'; + $repl = '@$1'; $text = preg_replace($pattern, $repl, $text); $pattern = '/[#]+([A-Za-z0-9-_]+)/'; - $repl = '#$1'; - $text = preg_replace($pattern, $repl, $text); + $repl = '#$1'; + $text = preg_replace($pattern, $repl, $text); - return filter_xss($text); + return $text; } -/* +/* * Implementation of hook_block_info() */ function twitter_pull_block_info() { @@ -223,12 +237,12 @@ function twitter_pull_block_info() { function twitter_pull_block_view($delta = '') { $info = twitter_pull_block_data(); $b_info = $info["$delta"]; - $content = twitter_pull_render($b_info->tweetkey, $b_info->title, $b_info->number_of_items, $b_info->theme_key, $b_info->lazy_load); + $content = twitter_pull_render($b_info->tweetkey, $b_info->title, $b_info->number_of_items, $b_info->theme_key); return array("subject"=>"", "content"=>$content); } -/* - * data hook for twitter_pull blocks. +/* + * data hook for twitter_pull blocks. * RETURN an array of data object with the following properties * delta (unique for all blocks) * name @@ -239,22 +253,21 @@ function twitter_pull_block_view($delta = '') { */ function twitter_pull_block_data() { static $data; - + //-- Static cache; if (!empty($data)) return $data; - + $data = module_invoke_all('twitter_pull_blocks'); drupal_alter('twitter_pull_data', $data); - + //-- Do some cleanup if (!empty($data) && is_array($data)) { foreach ($data as &$block) { //-- assign defaults - $block->title = (empty($block->title) && ($block->title !== FALSE)) ? $block->name : $block->title; - $block->number_of_items = (empty($block->number_of_items)) ? 5 : $block->number_of_items; - $block->theme_key = (empty($block->theme_key)) ? 'twitter_pull_listing' : $block->theme_key; - $block->lazy_load = (empty($block->lazy_load)) ? FALSE : $block->lazy_load; - } + $block->title = (empty($block->title)) ? $block->name : $block->title; + $block->number_of_items = (empty($block->number_of_items)) ? 5 : $block->number_of_items; + $block->theme_key = (empty($block->theme_key)) ? 'twitter_pull_listing' : $block->theme_key; + } } else { $data = array(); @@ -273,26 +286,6 @@ function twitter_pull_ctools_plugin_api($module, $api) { } /** - * Menu callback to provide lazy loading of tweets. - * - * @param $lazy_id - * The id containing all information needed to render - * the tweets with twitter_pull_render(). - */ -function twitter_pull_lazy($lazy_id) { - // Extract the parameters from the lazy id - $parameters = explode('-', $lazy_id, 4); - $twitkey = base64_decode($parameters[0]); - $title = base64_decode($parameters[1]); - $num_items = isset($parameters[2]) ? (int)$parameters[2] : NULL; - $themekey = isset($parameters[3]) ? check_plain($parameters[3]) : NULL; - - // Print the results - print twitter_pull_render($twitkey, $title, $num_items, $themekey, FALSE); -} - - -/** * Implements hook_boxes_plugins(). */ function twitter_pull_boxes_plugins() { @@ -310,20 +303,6 @@ function twitter_pull_boxes_plugins() { return $info; } -/** - * Implements hook_admin_menu_cache_info(). - */ -function twitter_pull_admin_menu_cache_info() { - $caches['twitter_pull'] = array( - 'title' => t('Twitter pull tweets'), - 'callback' => '_twitter_pull_cache_clear', - ); - return $caches; -} -/** - * Flushes cache tables used by the module. - */ -function _twitter_pull_cache_clear() { - cache_clear_all('*', TWITTER_PULL_CACHE_TABLE, TRUE); -} + +