Latest Tweets Widget - Version 1.0.0

Version Description

Download this release

Release Info

Developer timwhitlock
Plugin Icon 128x128 Latest Tweets Widget
Version 1.0.0
Comparing to
See all releases

Version 1.0.0

lang/README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ # Translating Twitter API Wordpress Plugin
2
+
3
+ Translations aren't implemented yet, but the plugin is set up for it.
4
+ If you'd like to translate this plugin into your language, please get in touch.
5
+
lang/twitter-api.pot ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ msgid ""
2
+ msgstr ""
3
+ "Project-Id-Version: Twitter API Wordpress plugin\n"
4
+ "Report-Msgid-Bugs-To: \n"
5
+ "POT-Creation-Date: Fri, 22 Feb 2013 17:50:38 +0000\n"
6
+ "PO-Revision-Date: Sun, 24 Feb 2013 11:44:51 +0000\n"
7
+ "Last-Translator: \n"
8
+ "Language-Team: \n"
9
+ "Language: English (UK)\n"
10
+ "MIME-Version: 1.0\n"
11
+ "Content-Type: text/plain; charset=UTF-8\n"
12
+ "Content-Transfer-Encoding: 8bit\n"
13
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14
+ "X-Generator: Loco http://localise.biz \n"
15
+ "X-Loco-Author: Tim Whitlock\n"
16
+ "X-Loco-Source-Locale: en_GB\n"
17
+ "X-Loco-Target-Locale: en_GB\n"
18
+ "X-Poedit-SourceCharset: UTF-8\n"
19
+
20
+ #: Asset just-now
21
+ msgid "Just now"
22
+ msgstr ""
23
+
24
+ #: Asset minutes-ago-singular
25
+ msgid "%u minute ago"
26
+ msgid_plural "%u minutes ago"
27
+ msgstr[0] ""
28
+ msgstr[1] ""
29
+
30
+ #: Asset hours-ago-singular
31
+ msgid "About %u hour ago"
32
+ msgid_plural "About %u hours ago"
33
+ msgstr[0] ""
34
+ msgstr[1] ""
35
+
36
+ #: Asset yesterday-at
37
+ msgid "Yesterday at "
38
+ msgstr ""
39
+
40
+ #: Asset http-status-100
41
+ msgid "Continue"
42
+ msgstr ""
43
+
44
+ #: Asset http-status-101
45
+ msgid "Switching Protocols"
46
+ msgstr ""
47
+
48
+ #: Asset http-status-200
49
+ msgid "OK"
50
+ msgstr ""
51
+
52
+ #: Asset http-status-201
53
+ msgid "Created"
54
+ msgstr ""
55
+
56
+ #: Asset http-status-202
57
+ msgid "Accepted"
58
+ msgstr ""
59
+
60
+ #: Asset http-status-203
61
+ msgid "Non-Authoritative Information"
62
+ msgstr ""
63
+
64
+ #: Asset http-status-204
65
+ msgid "No Content"
66
+ msgstr ""
67
+
68
+ #: Asset http-status-205
69
+ msgid "Reset Content"
70
+ msgstr ""
71
+
72
+ #: Asset http-status-206
73
+ msgid "Partial Content"
74
+ msgstr ""
75
+
76
+ #: Asset http-status-300
77
+ msgid "Multiple Choices"
78
+ msgstr ""
79
+
80
+ #: Asset http-status-301
81
+ msgid "Moved Permanently"
82
+ msgstr ""
83
+
84
+ #: Asset http-status-302
85
+ msgid "Found"
86
+ msgstr ""
87
+
88
+ #: Asset http-status-303
89
+ msgid "See Other"
90
+ msgstr ""
91
+
92
+ #: Asset http-status-304
93
+ msgid "Not Modified"
94
+ msgstr ""
95
+
96
+ #: Asset http-status-305
97
+ msgid "Use Proxy"
98
+ msgstr ""
99
+
100
+ #: Asset http-status-307
101
+ msgid "Temporary Redirect"
102
+ msgstr ""
103
+
104
+ #: Asset http-status-400
105
+ msgid "Bad Request"
106
+ msgstr ""
107
+
108
+ #: Asset http-status-401
109
+ msgid "Authorization Required"
110
+ msgstr ""
111
+
112
+ #: Asset http-status-402
113
+ msgid "Payment Required"
114
+ msgstr ""
115
+
116
+ #: Asset http-status-403
117
+ msgid "Forbidden"
118
+ msgstr ""
119
+
120
+ #: Asset http-status-404
121
+ msgid "Not Found"
122
+ msgstr ""
123
+
124
+ #: Asset http-status-405
125
+ msgid "Method Not Allowed"
126
+ msgstr ""
127
+
128
+ #: Asset http-status-406
129
+ msgid "Not Acceptable"
130
+ msgstr ""
131
+
132
+ #: Asset http-status-407
133
+ msgid "Proxy Authentication Required"
134
+ msgstr ""
135
+
136
+ #: Asset http-status-408
137
+ msgid "Request Time-out"
138
+ msgstr ""
139
+
140
+ #: Asset http-status-409
141
+ msgid "Conflict"
142
+ msgstr ""
143
+
144
+ #: Asset http-status-410
145
+ msgid "Gone"
146
+ msgstr ""
147
+
148
+ #: Asset http-status-411
149
+ msgid "Length Required"
150
+ msgstr ""
151
+
152
+ #: Asset http-status-412
153
+ msgid "Precondition Failed"
154
+ msgstr ""
155
+
156
+ #: Asset http-status-413
157
+ msgid "Request Entity Too Large"
158
+ msgstr ""
159
+
160
+ #: Asset http-status-414
161
+ msgid "Request-URI Too Large"
162
+ msgstr ""
163
+
164
+ #: Asset http-status-415
165
+ msgid "Unsupported Media Type"
166
+ msgstr ""
167
+
168
+ #: Asset http-status-416
169
+ msgid "Requested range not satisfiable"
170
+ msgstr ""
171
+
172
+ #: Asset http-status-417
173
+ msgid "Expectation Failed"
174
+ msgstr ""
175
+
176
+ #: Asset http-status-429
177
+ msgid "Twitter API rate limit exceeded"
178
+ msgstr ""
179
+
180
+ #: Asset http-status-500
181
+ msgid "Twitter server error"
182
+ msgstr ""
183
+
184
+ #: Asset http-status-501
185
+ msgid "Not Implemented"
186
+ msgstr ""
187
+
188
+ #: Asset http-status-502
189
+ msgid "Twitter is not responding"
190
+ msgstr ""
191
+
192
+ #: Asset http-status-503
193
+ msgid "Twitter is too busy to respond"
194
+ msgstr ""
195
+
196
+ #: Asset http-status-504
197
+ msgid "Gateway Time-out"
198
+ msgstr ""
199
+
200
+ #: Asset http-status-505
201
+ msgid "HTTP Version not supported"
202
+ msgstr ""
203
+
204
+ #: Asset notice-unconfigured
205
+ msgid "Twitter application is not fully configured"
206
+ msgstr ""
207
+
208
+ #: Asset warning-no-apc
209
+ msgid "Cannot enable Twitter API cache without APC extension"
210
+ msgstr ""
211
+
212
+ #: Asset warning-unauthed
213
+ msgid "Twitter client not authenticated"
214
+ msgstr ""
215
+
216
+ #: Asset error-twitter-invalid-param
217
+ msgid "Invalid Twitter parameter"
218
+ msgstr ""
219
+
220
+ #: Asset error-twitter-generic
221
+ msgid "Twitter error #%d"
222
+ msgstr ""
223
+
224
+ #: Asset error-twitter-malformed
225
+ msgid "Malformed response from Twitter"
226
+ msgstr ""
227
+
228
+ #: Asset error-wordpress-http
229
+ msgid "Wordpress HTTP request failure"
230
+ msgstr ""
231
+
232
+ #: Asset error-oauth-invalid-token
233
+ msgid "Invalid OAuth token"
234
+ msgstr ""
235
+
236
+ #: Asset error-oauth-empty-key
237
+ msgid "Key required even if secret is empty"
238
+ msgstr ""
239
+
240
+ #: Asset twitter-api-authentication-settings
241
+ msgid "Twitter API Authentication Settings"
242
+ msgstr ""
243
+
244
+ #: Asset label-details-available-in
245
+ msgid "These details are available in"
246
+ msgstr ""
247
+
248
+ #: Asset label-your-twitter-dashboard
249
+ msgid "your Twitter dashboard"
250
+ msgstr ""
251
+
252
+ #: Asset admin-fatal-denied
253
+ msgid "You don't have permission to manage Twitter API settings"
254
+ msgstr ""
255
+
256
+ #: Asset admin-notice-unconfigured
257
+ msgid "Twitter application not fully configured"
258
+ msgstr ""
259
+
260
+ #: Asset admin-notice-unauthed
261
+ msgid "Plugin not yet authenticated with Twitter"
262
+ msgstr ""
263
+
264
+ #: Asset admin-notice-authenticated
265
+ msgid "Authenticated as @%s"
266
+ msgstr ""
267
+
268
+ #: Asset label-twitter-api
269
+ msgid "Twitter API"
270
+ msgstr ""
latest-tweets.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Latest Tweets
4
+ Plugin URI: https://github.com/timwhitlock/wp-twitter-api
5
+ Description: Provides a sidebar widget showing latest tweets - compatible with the new Twitter API 1.1
6
+ Author: Tim Whitlock
7
+ Version: 1
8
+ Author URI: http://timwhitlock.info/
9
+ */
10
+
11
+
12
+
13
+ /**
14
+ * Pull latest tweets with some caching of raw data.
15
+ * @param string account whose tweets we're pulling
16
+ * @param int number of tweets to get and display
17
+ * @return array blocks of html expected by the widget
18
+ */
19
+ function latest_tweets_render( $screen_name, $count ){
20
+ if( ! function_exists('twitter_api_get') ){
21
+ require_once dirname(__FILE__).'/lib/twitter-api.php';
22
+ }
23
+ if( function_exists('apc_fetch') ){
24
+ // We could cache the rendered HTML, but this tests the twitter_api cache functions
25
+ twitter_api_enable_cache( 300 );
26
+ }
27
+ try {
28
+ // Note that exluding replies means we may get less than $count tweets.
29
+ // So we'll get more than we want and trim the result.
30
+ $tweets = twitter_api_get('statuses/user_timeline', array (
31
+ 'count' => 3 * $count,
32
+ 'exclude_replies' => true,
33
+ 'include_rts' => false,
34
+ 'trim_user' => true,
35
+ 'screen_name' => $screen_name,
36
+ ) );
37
+ if( isset($tweets[$count]) ){
38
+ $tweets = array_slice( $tweets, 0, $count );
39
+ }
40
+ }
41
+ catch( Exception $Ex ){
42
+ return array( '<p><strong>Error:</strong> '.esc_html($Ex->getMessage()).'</p>' );
43
+ }
44
+ // render each tweet as a blocks of html for the widget list items
45
+ twitter_api_include('utils');
46
+ $rendered = array();
47
+ foreach( $tweets as $tweet ){
48
+ extract( $tweet );
49
+ $link = esc_html( 'http://twitter.com/'.$screen_name.'/status/'.$id_str);
50
+ $date = esc_html( twitter_api_relative_date($created_at) );
51
+ $rendered[] = '<p class="text">'.twitter_api_html($text).'</p>'.
52
+ '<p class="details"><a href="'.$link.'" target="_blank"><time datetime="'.$created_at.'">'.$date.'</time></a></p>';
53
+ }
54
+ return $rendered;
55
+ }
56
+
57
+
58
+
59
+
60
+ /**
61
+ * Example latest tweets widget class
62
+ */
63
+ class Latest_Tweets_Widget extends WP_Widget {
64
+
65
+ /** @see WP_Widget::__construct */
66
+ public function __construct( $id_base = false, $name = 'Latest Tweets', $widget_options = array(), $control_options = array() ){
67
+ $this->options = array(
68
+ array (
69
+ 'name' => 'title',
70
+ 'label' => 'Widget title',
71
+ 'type' => 'text'
72
+ ),
73
+ array (
74
+ 'name' => 'screen_name',
75
+ 'label' => 'Twitter handle',
76
+ 'type' => 'text'
77
+ ),
78
+ array (
79
+ 'name' => 'num',
80
+ 'label' => 'Number of tweets',
81
+ 'type' => 'text'
82
+ ),
83
+ );
84
+ parent::__construct( $id_base, $name, $widget_options, $control_options );
85
+ }
86
+
87
+ /** @see WP_Widget::form */
88
+ public function form( $instance ) {
89
+ if ( empty($instance) ) {
90
+ $instance['title'] = 'Latest Tweets';
91
+ $instance['screen_name'] = '';
92
+ $instance['num'] = '5';
93
+ }
94
+ foreach ( $this->options as $val ) {
95
+ $label = '<label for="'.$this->get_field_id($val['name']).'">'.$val['label'].'</label>';
96
+ echo '<p>'.$label.'<br />';
97
+ echo '<input class="widefat" id="'.$this->get_field_id($val['name']).'" name="'.$this->get_field_name($val['name']).'" type="text" value="'.esc_attr($instance[$val['name']]).'" /></p>';
98
+ }
99
+ }
100
+
101
+ /** @see WP_Widget::widget */
102
+ public function widget( array $args, $instance ) {
103
+ $title = apply_filters('widget_title', $instance['title']);
104
+ echo $args['before_widget'], '<div class="latest-tweets">';
105
+ echo $args['before_title'], $instance['title'], $args['after_title'];
106
+ echo '<ul class="latest-tweets">';
107
+ foreach( latest_tweets_render( $instance['screen_name'], $instance['num'] ) as $tweet ){
108
+ echo '<li class="latest-tweet">',$tweet,'</li>';
109
+ }
110
+ echo '</ul>';
111
+ echo '</div>',$args['after_widget'];
112
+ }
113
+
114
+ }
115
+
116
+
117
+
118
+ add_action( 'widgets_init', function(){ return register_widget('Latest_Tweets_Widget'); } );
119
+
120
+
121
+ if( is_admin() ){
122
+ require_once dirname(__FILE__).'/lib/twitter-api.php';
123
+ }
lib/twitter-api-admin.php ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Twitter API admin functions.
4
+ * Configures and authenticates with the Twitter API.
5
+ * @author Tim Whitlock <@timwhitlock>
6
+ */
7
+
8
+
9
+
10
+ /**
11
+ * Open admin page with header and message
12
+ */
13
+ function twitter_api_admin_render_header( $subheader, $css = '' ){
14
+ ?>
15
+ <div class="wrap">
16
+ <h2><?php echo esc_html__('Twitter API Authentication Settings')?></h2>
17
+ <div class="<?php echo $css?>">
18
+ <h3><?php echo esc_html($subheader)?></h3>
19
+ </div>
20
+ <?php
21
+ }
22
+
23
+
24
+
25
+ /**
26
+ * Close admin page
27
+ */
28
+ function twitter_api_admin_render_footer(){
29
+ ?>
30
+ </div>
31
+ <?php
32
+ }
33
+
34
+
35
+
36
+ /**
37
+ * Render form for viewing/editing of application settings
38
+ */
39
+ function twitter_api_admin_render_form(){
40
+ extract( _twitter_api_config() );
41
+ ?>
42
+ <form action="<?php echo twitter_api_admin_base_uri()?>" method="post">
43
+ <p>
44
+ <label for="twitter-api--consumer-key">OAuth Consumer Key:</label><br />
45
+ <input type="text" size="64" name="saf_twitter[consumer_key]" id="twitter-api--consumer-key" value="<?php echo esc_html($consumer_key)?>" />
46
+ </p>
47
+ <p>
48
+ <label for="twitter-api--consumer-secret">OAuth Consumer Secret:</label><br />
49
+ <input type="text" size="64" name="saf_twitter[consumer_secret]" id="twitter-api--consumer-secret" value="<?php echo esc_html($consumer_secret)?>" />
50
+ </p>
51
+ <p>
52
+ <label for="twitter-api--access-key">OAuth Access Token:</label><br />
53
+ <input type="text" size="64" name="saf_twitter[access_key]" id="twitter-api--access-key" value="<?php echo esc_html($access_key)?>" />
54
+ </p>
55
+ <p>
56
+ <label for="twitter-api--access-secret">OAuth Access Secret:</label><br />
57
+ <input type="text" size="64" name="saf_twitter[access_secret]" id="twitter-api--access-secret" value="<?php echo esc_html($access_secret)?>" />
58
+ </p>
59
+ <p class="submit">
60
+ <input type="submit" class="button-primary" value="Save settings" />
61
+ </p>
62
+ <small>
63
+ <?php echo esc_html__('These details are available in')?>
64
+ <a href="https://dev.twitter.com/apps"><?php echo esc_html__('your Twitter dashboard')?></a>
65
+ </small>
66
+ </form>
67
+ <?php
68
+ }
69
+
70
+
71
+
72
+ /**
73
+ * Render "Connect" button for authenticating at twitter.com
74
+ * @param string OAuth application Consumer Key
75
+ * @param string OAuth application Consumer Secret
76
+ */
77
+ function twitter_api_admin_render_login( $consumer_key, $consumer_secret ){
78
+ try {
79
+ $callback = twitter_api_admin_base_uri();
80
+ $Token = twitter_api_oauth_request_token( $consumer_key, $consumer_secret, $callback );
81
+ }
82
+ catch( Exception $Ex ){
83
+ echo '<div class="error"><p><strong>Error:</strong> ',esc_html( $Ex->getMessage() ),'</p></div>';
84
+ return;
85
+ }
86
+ // Remember request token and render link to authorize
87
+ // we're storing permanently - not using session here, because WP provides no session API.
88
+ _twitter_api_config( array( 'request_secret' => $Token->secret ) );
89
+ $href = TWITTER_OAUTH_AUTHORIZE_URL.'?oauth_token='.rawurlencode($Token->key);
90
+ echo '<p><a class="button-primary" href="',esc_html($href),'">Connect to Twitter</a></p>';
91
+ echo '<p>&nbsp;</p>';
92
+ }
93
+
94
+
95
+
96
+
97
+ /**
98
+ * Render full admin page
99
+ */
100
+ function twitter_api_admin_render_page(){
101
+ if ( ! current_user_can('manage_options') ){
102
+ twitter_api_admin_render_header( __("You don't have permission to manage Twitter API settings"),'error');
103
+ twitter_api_admin_render_footer();
104
+ return;
105
+ }
106
+ try {
107
+
108
+ // update applicaion settings if posted
109
+ if( isset($_POST['saf_twitter']) && is_array( $update = $_POST['saf_twitter'] ) ){
110
+ $conf = _twitter_api_config( $update );
111
+ }
112
+
113
+ // else get current settings
114
+ else {
115
+ $conf = _twitter_api_config();
116
+ }
117
+
118
+ // check whether we have any OAuth params
119
+ extract( $conf );
120
+ if( ! $consumer_key || ! $consumer_secret ){
121
+ throw new Exception( __('Twitter application not fully configured') );
122
+ }
123
+
124
+ // else exchange access token if callback // request secret saved as option
125
+ if( isset($_GET['oauth_token']) && isset($_GET['oauth_verifier']) ){
126
+ $Token = twitter_api_oauth_access_token( $consumer_key, $consumer_secret, $_GET['oauth_token'], $request_secret, $_GET['oauth_verifier'] );
127
+ // have access token, update config and destroy request secret
128
+ $conf = _twitter_api_config( array(
129
+ 'request_secret' => '',
130
+ 'access_key' => $Token->key,
131
+ 'access_secret' => $Token->secret,
132
+ ) );
133
+ extract( $conf );
134
+ // fall through to verification of credentials
135
+ }
136
+
137
+ // else administrator needs to connect / authenticate with Twitter.
138
+ if( ! $access_key || ! $access_secret ){
139
+ twitter_api_admin_render_header( __('Plugin not yet authenticated with Twitter'), 'error' );
140
+ twitter_api_admin_render_login( $consumer_key, $consumer_secret );
141
+ }
142
+
143
+ // else we have auth - verify that tokens are all still valid
144
+ else {
145
+ $me = twitter_api_get('account/verify_credentials');
146
+ twitter_api_admin_render_header( sprintf( __('Authenticated as @%s'), $me['screen_name'] ), 'updated' );
147
+ }
148
+
149
+ }
150
+ catch( TwitterApiException $Ex ){
151
+ twitter_api_admin_render_header( $Ex->getStatus().': Error '.$Ex->getCode().', '.$Ex->getMessage(), 'error' );
152
+ if( 401 === $Ex->getStatus() ){
153
+ twitter_api_admin_render_login( $consumer_key, $consumer_secret );
154
+ }
155
+ }
156
+ catch( Exception $Ex ){
157
+ twitter_api_admin_render_header( $Ex->getMessage(), 'error' );
158
+ }
159
+
160
+ // end admin page with options form and close wrapper
161
+ twitter_api_admin_render_form();
162
+ twitter_api_admin_render_footer();
163
+ }
164
+
165
+
166
+
167
+ /**
168
+ * Calculate base URL for admin OAuth callbacks
169
+ * @return string
170
+ */
171
+ function twitter_api_admin_base_uri(){
172
+ static $base_uri;
173
+ if( ! isset($base_uri) ){
174
+ $port = isset($_SERVER['HTTP_X_FORWARDED_PORT']) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $_SERVER['SERVER_PORT'];
175
+ $prot = '443' === $port ? 'https:' : 'http:';
176
+ $base_uri = $prot.'//'.$_SERVER['HTTP_HOST'].''.current( explode( '&', $_SERVER['REQUEST_URI'], 2 ) );
177
+ }
178
+ return $base_uri;
179
+ }
180
+
181
+
182
+
183
+
184
+
185
+ /**
186
+ * Admin menu registration callback
187
+ */
188
+ function twitter_api_admin_menu() {
189
+ add_options_page( __('Twitter API'), __('Twitter API'), 'manage_options', 'twitter-api-admin', 'twitter_api_admin_render_page');
190
+ }
191
+
192
+
193
+
194
+ // register our admin page with the menu, and we're done.
195
+ add_action('admin_menu', 'twitter_api_admin_menu');
196
+
197
+
198
+
lib/twitter-api-core.php ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Twitter API client and related utilities.
4
+ * Uses built-in wordpress HTTP utilities.
5
+ * @author Tim Whitlock <@timwhitlock>
6
+ */
7
+
8
+
9
+ define('TWITTER_API_BASE', 'https://api.twitter.com/1.1' );
10
+
11
+ define('TWITTER_OAUTH_REQUEST_TOKEN_URL', 'https://twitter.com/oauth/request_token');
12
+
13
+ define('TWITTER_OAUTH_AUTHORIZE_URL', 'https://twitter.com/oauth/authorize');
14
+
15
+ define('TWITTER_OAUTH_AUTHENTICATE_URL', 'https://twitter.com/oauth/authenticate');
16
+
17
+ define('TWITTER_OAUTH_ACCESS_TOKEN_URL', 'https://twitter.com/oauth/access_token');
18
+
19
+
20
+
21
+ /**
22
+ * Get config options from DB
23
+ * @param array any new options to update
24
+ */
25
+ function _twitter_api_config( array $update = array() ){
26
+ static $conf;
27
+ if( ! isset($conf) ){
28
+ $conf = array (
29
+ 'consumer_key' => '',
30
+ 'consumer_secret' => '',
31
+ 'request_secret' => '',
32
+ 'access_key' => '',
33
+ 'access_secret' => '',
34
+ );
35
+ foreach( $conf as $key => $val ){
36
+ $conf[$key] = get_option('twitter_api_'.$key) or
37
+ $conf[$key] = $val;
38
+ }
39
+ }
40
+ foreach( $update as $key => $val ){
41
+ if( isset($conf[$key]) ){
42
+ update_option( 'twitter_api_'.$key, $val );
43
+ $conf[$key] = $val;
44
+ }
45
+ }
46
+ return $conf;
47
+ }
48
+
49
+
50
+
51
+
52
+ /**
53
+ * Client for the Twitter REST API 1.1
54
+ */
55
+ class TwitterApiClient {
56
+
57
+ /**
58
+ * Consumer key token for application
59
+ * @var TwitterOAuthToken
60
+ */
61
+ private $Consumer;
62
+
63
+ /**
64
+ * Authenticated access token
65
+ * @var TwitterOAuthToken
66
+ */
67
+ private $AccessToken;
68
+
69
+ /**
70
+ * Whether caching API GET requests
71
+ * @var int
72
+ */
73
+ private $cache_ttl = null;
74
+
75
+ /**
76
+ * Namespace/prefix for cache keys
77
+ * @var string
78
+ */
79
+ private $cache_ns;
80
+
81
+ /**
82
+ * Registry of last rate limit arrays by full api function call
83
+ * @var array
84
+ */
85
+ private $last_rate = array();
86
+
87
+ /**
88
+ * Last api function called, e.g. "direct_messages/sent"
89
+ * @var string
90
+ */
91
+ private $last_call;
92
+
93
+
94
+ /**
95
+ * Get client instance authenticated with 'system' credentials
96
+ * @param bool whether we're getting the system default client which is expected to be authed
97
+ * @return TwitterApiClient
98
+ */
99
+ public static function create_instance( $default = true ){
100
+ $Client = new TwitterApiClient;
101
+ extract( _twitter_api_config() );
102
+ if( $default ){
103
+ if( ! $consumer_key || ! $consumer_secret || ! $access_key || ! $access_secret ){
104
+ trigger_error( __('Twitter application is not fully configured') );
105
+ }
106
+ $Client->set_oauth( $consumer_key, $consumer_secret, $access_key, $access_secret );
107
+ }
108
+ else if( $consumer_key && $consumer_secret ){
109
+ $Client->set_oauth( $consumer_key, $consumer_secret );
110
+ }
111
+ return $Client;
112
+ }
113
+
114
+
115
+ /**
116
+ * @internal
117
+ */
118
+ public function __sleep(){
119
+ return array('User','Consumer','AccessToken');
120
+ }
121
+
122
+ /**
123
+ * Enable caching of subsequent API calls
124
+ * @return TwitterApiClient
125
+ */
126
+ public function enable_cache( $ttl = 0, $namespace = 'wp_twitter_api_' ){
127
+ if( function_exists('apc_store') ){
128
+ $this->cache_ttl = (int) $ttl;
129
+ $this->cache_ns = $namespace;
130
+ return $this;
131
+ }
132
+ trigger_error( __('Cannot enable Twitter API cache without APC extension') );
133
+ return $this->disable_cache();
134
+ }
135
+
136
+ /**
137
+ * Disable caching for susequent API calls
138
+ * @return TwitterApiClient
139
+ */
140
+ public function disable_cache(){
141
+ $this->cache_ttl = null;
142
+ $this->cache_ns = null;
143
+ return $this;
144
+ }
145
+
146
+ /**
147
+ * Test whether the client has full authentication data.
148
+ * Warning: does not validate credentials
149
+ * @return bool
150
+ */
151
+ public function has_auth(){
152
+ return $this->AccessToken instanceof TwitterOAuthToken && $this->AccessToken->secret;
153
+ }
154
+
155
+ /**
156
+ * Unset all logged in credentials - useful in error situations
157
+ * @return TwitterApiClient
158
+ */
159
+ public function deauthorize(){
160
+ $this->AccessToken = null;
161
+ return $this;
162
+ }
163
+
164
+ /**
165
+ * Set currently logged in user's OAuth access token
166
+ * @param string consumer api key
167
+ * @param string consumer secret
168
+ * @param string access token
169
+ * @param string access token secret
170
+ * @return TwitterApiClient
171
+ */
172
+ public function set_oauth( $consumer_key, $consumer_secret, $access_key = '', $access_secret = '' ){
173
+ $this->deauthorize();
174
+ $this->Consumer = new TwitterOAuthToken( $consumer_key, $consumer_secret );
175
+ if( $access_key && $access_secret ){
176
+ $this->AccessToken = new TwitterOAuthToken( $access_key, $access_secret );
177
+ }
178
+ return $this;
179
+ }
180
+
181
+
182
+ /**
183
+ * Call API method over HTTP and return raw data
184
+ * @param string API method, e.g. "users/show"
185
+ * @param array method arguments
186
+ * @param string http request method
187
+ * @return array unserialized data returned from twitter
188
+ * @throws TwitterApiException
189
+ */
190
+ public function call( $path, array $_args, $http_method ){
191
+ // all calls must be authenticated in API 1.1
192
+ if( ! $this->has_auth() ){
193
+ throw new TwitterApiException( __('Twitter client not authenticated'), 0, 401 );
194
+ }
195
+ // transform some arguments and ensure strings
196
+ // no further validation is performed
197
+ $args = array();
198
+ foreach( $_args as $key => $val ){
199
+ if( is_string($val) ){
200
+ $args[$key] = $val;
201
+ }
202
+ else if( true === $val ){
203
+ $args[$key] = 'true';
204
+ }
205
+ else if( false === $val || null === $val ){
206
+ $args[$key] = 'false';
207
+ }
208
+ else if( ! is_scalar($val) ){
209
+ throw new TwitterApiException( __('Invalid Twitter parameter').' ('.gettype($val).') '.$key.' in '.$path, -1 );
210
+ }
211
+ else {
212
+ $args[$key] = (string) $val;
213
+ }
214
+ }
215
+ // Fetch response from cache if possible / allowed / enabled
216
+ if( $http_method === 'GET' && isset($this->cache_ttl) ){
217
+ $cachekey = $this->cache_ns.$path.'_'.md5( serialize($args) );
218
+ if( preg_match('/^(\d+)-/', $this->AccessToken->key, $reg ) ){
219
+ $cachekey .= '_'.$reg[1];
220
+ }
221
+ $data = apc_fetch( $cachekey );
222
+ if( is_array($data) ){
223
+ return $data;
224
+ }
225
+ }
226
+ // @todo could validate args against endpoints here.
227
+
228
+ // Using Wordpress WP_Http for requests, see class-http.php
229
+ $conf = array (
230
+ 'method' => $http_method,
231
+ 'redirection' => 0,
232
+ );
233
+ // build signed URL and request parameters
234
+ $endpoint = TWITTER_API_BASE.'/'.$path.'.json';
235
+ $params = new TwitterOAuthParams( $args );
236
+ $params->set_consumer( $this->Consumer );
237
+ $params->set_token( $this->AccessToken );
238
+ $params->sign_hmac( $http_method, $endpoint );
239
+ if( 'GET' === $http_method ){
240
+ $endpoint .= '?'.$params->serialize();
241
+ }
242
+ else {
243
+ //$conf['headers'] = $params->oauth_header();
244
+ $conf['body'] = $params->serialize();
245
+ }
246
+ $http = self::http_request( $endpoint, $conf );
247
+ $data = json_decode( $http['body'], true );
248
+ $status = $http['response']['code'];
249
+ // unserializable array assumed to be serious error
250
+ if( ! is_array($data) ){
251
+ $err = array(
252
+ 'message' => __($http['response']['message']),
253
+ 'code' => -1
254
+ );
255
+ TwitterApiException::chuck( $err, $status );
256
+ }
257
+ // else could be well-formed error
258
+ if( isset( $data['errors'] ) ) {
259
+ while( $err = array_shift($data['errors']) ){
260
+ $err['message'] = __( $err['message'] );
261
+ if( $data['errors'] ){
262
+ $message = sprintf( __('Twitter error #%d'), $err['code'] ).' "'.$err['message'].'"';
263
+ trigger_error( $message, E_USER_WARNING );
264
+ }
265
+ else {
266
+ TwitterApiException::chuck( $err, $status );
267
+ }
268
+ }
269
+ }
270
+ if( isset($cachekey) ){
271
+ apc_store( $cachekey, $data, $this->cache_ttl );
272
+ }
273
+ // remember current rate limits for this endpoint
274
+ $this->last_call = $path;
275
+ if( isset($http['headers']['x-rate-limit-limit']) ) {
276
+ $this->last_rate[$path] = array (
277
+ 'limit' => (int) $http['headers']['x-rate-limit-limit'],
278
+ 'remaining' => (int) $http['headers']['x-rate-limit-remaining'],
279
+ 'reset' => (int) $http['headers']['x-rate-limit-reset'],
280
+ );
281
+ }
282
+ return $data;
283
+ }
284
+
285
+
286
+
287
+ /**
288
+ * Perform an OAuth request - these differ somewhat from regular API calls
289
+ * @internal
290
+ */
291
+ function oauth_exchange( $endpoint, array $args ){
292
+ // build a post request and authenticate via OAuth header
293
+ $params = new TwitterOAuthParams( $args );
294
+ $params->set_consumer( $this->Consumer );
295
+ if( $this->AccessToken ){
296
+ $params->set_token( $this->AccessToken );
297
+ }
298
+ $params->sign_hmac( 'POST', $endpoint );
299
+ $conf = array (
300
+ 'method' => 'POST',
301
+ 'redirection' => 0,
302
+ 'headers' => array( 'Authorization' => $params->oauth_header() ),
303
+ );
304
+ $http = self::http_request( $endpoint, $conf, 200 );
305
+ $body = trim( $http['body'] );
306
+ $stat = $http['response']['code'];
307
+ if( 200 !== $stat ){
308
+ throw new TwitterApiException( $body, -1, $stat );
309
+ }
310
+ parse_str( $body, $params );
311
+ if( ! is_array($params) || ! isset($params['oauth_token']) || ! isset($params['oauth_token_secret']) ){
312
+ throw new TwitterApiException( __('Malformed response from Twitter'), -1, $stat );
313
+ }
314
+ return $params;
315
+ }
316
+
317
+
318
+
319
+ /**
320
+ * Abstract Wordpress HTTP call with error handling
321
+ * @return array response from wordpress functions
322
+ */
323
+ public static function http_request( $endpoint, array $conf ){
324
+ $http = wp_remote_request( $endpoint, $conf );
325
+ if( $http instanceof WP_Error ){
326
+ foreach( $http->get_error_messages() as $message ){
327
+ throw new TwitterApiException( $message, -1 );
328
+ }
329
+ }
330
+ if( empty($http['response']) ){
331
+ throw new TwitterApiException( __('Wordpress HTTP request failure'), -1 );
332
+ }
333
+ return $http;
334
+ }
335
+
336
+
337
+
338
+ /**
339
+ * Get current rate limit, if known. does not look it up
340
+ */
341
+ public function last_rate_limit( $func = '' ){
342
+ $func or $func = $this->last_call;
343
+ return isset($this->last_rate[$func]) ? $this->last_rate[$func] : array();
344
+ }
345
+
346
+
347
+ }
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+ /**
356
+ * Simple token class that holds key and secret
357
+ * @internal
358
+ */
359
+ class TwitterOAuthToken {
360
+
361
+ public $key;
362
+ public $secret;
363
+
364
+ function __construct( $key, $secret = '' ){
365
+ if( ! $key ){
366
+ throw new Exception( __('Invalid OAuth token').' - '.__('Key required even if secret is empty') );
367
+ }
368
+ $this->key = $key;
369
+ $this->secret = $secret;
370
+ }
371
+
372
+ }
373
+
374
+
375
+
376
+
377
+
378
+ /**
379
+ * Class for compiling, signing and serializing OAuth parameters
380
+ * @internal
381
+ */
382
+ class TwitterOAuthParams {
383
+
384
+ private $args;
385
+ private $consumer_secret;
386
+ private $token_secret;
387
+
388
+ private static function urlencode( $val ){
389
+ return str_replace( '%7E', '~', rawurlencode($val) );
390
+ }
391
+
392
+ public function __construct( array $args = array() ){
393
+ $this->args = $args + array (
394
+ 'oauth_version' => '1.0',
395
+ );
396
+ }
397
+
398
+ public function set_consumer( TwitterOAuthToken $Consumer ){
399
+ $this->consumer_secret = $Consumer->secret;
400
+ $this->args['oauth_consumer_key'] = $Consumer->key;
401
+ }
402
+
403
+ public function set_token( TwitterOAuthToken $Token ){
404
+ $this->token_secret = $Token->secret;
405
+ $this->args['oauth_token'] = $Token->key;
406
+ }
407
+
408
+ private function normalize(){
409
+ $flags = SORT_STRING | SORT_ASC;
410
+ ksort( $this->args, $flags );
411
+ foreach( $this->args as $k => $a ){
412
+ if( is_array($a) ){
413
+ sort( $this->args[$k], $flags );
414
+ }
415
+ }
416
+ return $this->args;
417
+ }
418
+
419
+ public function serialize(){
420
+ $str = http_build_query( $this->args );
421
+ $str = str_replace( '%7E', '~', $str );
422
+ return $str;
423
+ }
424
+
425
+ public function sign_hmac( $http_method, $http_rsc ){
426
+ $this->args['oauth_signature_method'] = 'HMAC-SHA1';
427
+ $this->args['oauth_timestamp'] = sprintf('%u', time() );
428
+ $this->args['oauth_nonce'] = sprintf('%f', microtime(true) );
429
+ unset( $this->args['oauth_signature'] );
430
+ $this->normalize();
431
+ $str = $this->serialize();
432
+ $str = strtoupper($http_method).'&'.self::urlencode($http_rsc).'&'.self::urlencode($str);
433
+ $key = self::urlencode($this->consumer_secret).'&'.self::urlencode($this->token_secret);
434
+ $this->args['oauth_signature'] = base64_encode( hash_hmac( 'sha1', $str, $key, true ) );
435
+ return $this->args;
436
+ }
437
+
438
+ public function oauth_header(){
439
+ $lines = array();
440
+ foreach( $this->args as $key => $val ){
441
+ $lines[] = self::urlencode($key).'="'.self::urlencode($val).'"';
442
+ }
443
+ return 'OAuth '.implode( ",\n ", $lines );
444
+ }
445
+
446
+ }
447
+
448
+
449
+
450
+
451
+
452
+
453
+ /**
454
+ * HTTP status codes with some overridden for Twitter-related messages.
455
+ * Note these do not replace error text from Twitter, they're for complete API failures.
456
+ * @param int HTTP status code
457
+ * @return string HTTP status text
458
+ */
459
+ function _twitter_api_http_status_text( $s ){
460
+ static $codes = array (
461
+ 100 => 'Continue',
462
+ 101 => 'Switching Protocols',
463
+
464
+ 200 => 'OK',
465
+ 201 => 'Created',
466
+ 202 => 'Accepted',
467
+ 203 => 'Non-Authoritative Information',
468
+ 204 => 'No Content',
469
+ 205 => 'Reset Content',
470
+ 206 => 'Partial Content',
471
+
472
+ 300 => 'Multiple Choices',
473
+ 301 => 'Moved Permanently',
474
+ 302 => 'Found',
475
+ 303 => 'See Other',
476
+ 304 => 'Not Modified',
477
+ 305 => 'Use Proxy',
478
+ 307 => 'Temporary Redirect',
479
+
480
+ 400 => 'Bad Request',
481
+ 401 => 'Authorization Required',
482
+ 402 => 'Payment Required',
483
+ 403 => 'Forbidden',
484
+ 404 => 'Not Found',
485
+ 405 => 'Method Not Allowed',
486
+ 406 => 'Not Acceptable',
487
+ 407 => 'Proxy Authentication Required',
488
+ 408 => 'Request Time-out',
489
+ 409 => 'Conflict',
490
+ 410 => 'Gone',
491
+ 411 => 'Length Required',
492
+ 412 => 'Precondition Failed',
493
+ 413 => 'Request Entity Too Large',
494
+ 414 => 'Request-URI Too Large',
495
+ 415 => 'Unsupported Media Type',
496
+ 416 => 'Requested range not satisfiable',
497
+ 417 => 'Expectation Failed',
498
+ // ..
499
+ 429 => 'Twitter API rate limit exceeded',
500
+
501
+ 500 => 'Twitter server error',
502
+ 501 => 'Not Implemented',
503
+ 502 => 'Twitter is not responding',
504
+ 503 => 'Twitter is too busy to respond',
505
+ 504 => 'Gateway Time-out',
506
+ 505 => 'HTTP Version not supported',
507
+ );
508
+ return __( isset($codes[$s]) ? $codes[$s] : sprintf('Status %u from Twitter', $s) );
509
+ }
510
+
511
+
512
+
513
+
514
+
515
+ /**
516
+ * Exception for throwing when Twitter responds with something unpleasant
517
+ */
518
+ class TwitterApiException extends Exception {
519
+
520
+ /**
521
+ * HTTP Status of error
522
+ * @var int
523
+ */
524
+ protected $status = 0;
525
+
526
+
527
+ /**
528
+ * Throw appropriate exception type according to HTTP status code
529
+ * @param array Twitter error data from their response
530
+ */
531
+ public static function chuck( array $err, $status ){
532
+ $code = isset($err['code']) ? (int) $err['code'] : -1;
533
+ $mess = isset($err['message']) ? trim($err['message']) : '';
534
+ static $classes = array (
535
+ 404 => 'TwitterApiNotFoundException',
536
+ 429 => 'TwitterApiRateLimitException',
537
+ );
538
+ $eclass = isset($classes[$status]) ? $classes[$status] : __CLASS__;
539
+ throw new $eclass( $mess, $code, $status );
540
+ }
541
+
542
+
543
+ /**
544
+ * Construct TwitterApiException with addition of HTTP status code.
545
+ * @overload
546
+ */
547
+ public function __construct( $message, $code = 0 ){
548
+ if( 2 < func_num_args() ){
549
+ $this->status = (int) func_get_arg(2);
550
+ }
551
+ if( ! $message ){
552
+ $message = _twitter_api_http_status_text($this->status);
553
+ }
554
+ parent::__construct( $message, $code );
555
+ }
556
+
557
+
558
+ /**
559
+ * Get HTTP status of error
560
+ * @return int
561
+ */
562
+ public function getStatus(){
563
+ return $this->status;
564
+ }
565
+
566
+ }
567
+
568
+
569
+ /** 404 */
570
+ class TwitterApiNotFoundException extends TwitterApiException {
571
+
572
+ }
573
+
574
+
575
+ /** 429 */
576
+ class TwitterApiRateLimitException extends TwitterApiException {
577
+
578
+ }
579
+
580
+
581
+
582
+
583
+ /**
584
+ * Enable localisation when ready.
585
+ * Currently merging into default domain.
586
+ * This works, but no translations available yet.
587
+ *
588
+ function _twitter_api_init_l10n(){
589
+ $locales = array( get_locale(), 'en_GB' );
590
+ while( $locale = array_shift($locales) ){
591
+ $mo = twitter_api_basedir().'/lang/twitter-api-'.$locale.'.mo';
592
+ if( file_exists($mo) ){
593
+ load_textdomain( 'default', $mo );
594
+ return;
595
+ }
596
+ }
597
+ }
598
+
599
+ add_action( 'init', '_twitter_api_init_l10n' );
600
+ */
601
+
602
+
603
+
604
+
lib/twitter-api-utils.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Helper utilities for working with Twitter API data.
4
+ * @author Tim Whitlock
5
+ */
6
+
7
+
8
+
9
+
10
+ /**
11
+ * Utility for rendering tweet text with clickable links
12
+ * @param string plain text tweet
13
+ * @param string optional target for links, defaults to _blank
14
+ * @param bool optionally specify that passed text is already escaped HTML
15
+ * @return string HTML source of tweet text
16
+ */
17
+ function twitter_api_html( $src, $target = '_blank', $alreadyhtml = false ){
18
+ if( ! $alreadyhtml ){
19
+ $src = esc_html( $src );
20
+ }
21
+ // linkify URLs
22
+ $src = preg_replace('!https?://\S+!', '<a href="\\0" target="'.$target.'">\\0</a>', $src );
23
+ // linkify @names
24
+ $src = preg_replace('!@([a-z0-9_]{1,15})!i', '<a class="twitter-screen-name" href="https://twitter.com/\\1" target="'.$target.'">\\0</a>', $src );
25
+ // linkify #hashtags
26
+ $src = preg_replace('/(?<!&)#(\w+)/i', '<a class="twitter-hashtag" href="https://twitter.com/search?q=%23\\1&amp;src=hash" target="'.$target.'">\\0</a>', $src );
27
+ return $src;
28
+ }
29
+
30
+
31
+
32
+
33
+ /**
34
+ * Utility converts the date [of a tweet] to relative time descriprion, e.g. about 2 minutes ago
35
+ *
36
+ */
37
+ function twitter_api_relative_date( $strdate ){
38
+ // get universal time now.
39
+ static $t, $y, $m, $d, $h, $i, $s, $o;
40
+ if( ! isset($t) ){
41
+ $t = time();
42
+ sscanf(gmdate('Y m d H i s',$t), '%u %u %u %u %u %u', $y,$m,$d,$h,$i,$s);
43
+ }
44
+ // get universal time of tweet
45
+ $tt = is_int($strdate) ? $strdate : strtotime($strdate);
46
+ if( ! $tt || $tt > $t ){
47
+ // slight difference between our clock and Twitter's clock can cause problem here - just pretend it was zero seconds ago
48
+ $tt = $t;
49
+ $tdiff = 0;
50
+ }
51
+ else {
52
+ sscanf(gmdate('Y m d H i s',$tt), '%u %u %u %u %u %u', $yy,$mm,$dd,$hh,$ii,$ss);
53
+ // Calculate relative date string
54
+ $tdiff = $t - $tt;
55
+ }
56
+ // Less than a minute ago?
57
+ if( $tdiff < 60 ){
58
+ return __('Just now');
59
+ }
60
+ // within last hour? X minutes ago
61
+ if( $tdiff < 3600 ){
62
+ $idiff = (int) floor( $tdiff / 60 );
63
+ return sprintf( _n( '%u minute ago', '%u minutes ago', $idiff ), $idiff );
64
+ }
65
+ // within same day? About X hours ago
66
+ $samey = ($y === $yy) and
67
+ $samem = ($m === $mm) and
68
+ $samed = ($d === $dd);
69
+ if( ! empty($samed) ){
70
+ $hdiff = (int) floor( $tdiff / 3600 );
71
+ return sprintf( _n( 'About %u hour ago', 'About %u hours ago', $hdiff ), $hdiff );
72
+ }
73
+ //
74
+ static $dt;
75
+ if( ! isset($dt) ){
76
+ $tz = ini_get('date.timezone') or $tz = 'Europe/London';
77
+ $tz = new DateTimeZone( $tz );
78
+ $dt = new DateTime;
79
+ $dt->setTimezone( $tz );
80
+ }
81
+ $dt->setTimestamp( $tt );
82
+ // within 24 hours?
83
+ if( $tdiff < 86400 ){
84
+ return __('Yesterday at ').$dt->format('g:i A');
85
+ }
86
+ // else return formatted date, e.g. "Oct 20th 2008 9:27 PM GMT" */
87
+ return $dt->format('M jS Y g:i A');
88
+ }
89
+
90
+
91
+
92
+
93
+
lib/twitter-api.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Twitter API, built from: https://github.com/timwhitlock/wp-twitter-api
4
+ */
5
+
6
+
7
+
8
+
9
+ /**
10
+ * Call a Twitter API GET method.
11
+ *
12
+ * @param string endpoint/method, e.g. "users/show"
13
+ * @param array Request arguments, e.g. array( 'screen_name' => 'timwhitlock' )
14
+ * @return array raw, deserialised data from Twitter
15
+ * @throws TwitterApiException
16
+ */
17
+ function twitter_api_get( $path, array $args = array() ){
18
+ $Client = twitter_api_client();
19
+ return $Client->call( $path, $args, 'GET' );
20
+ }
21
+
22
+
23
+
24
+
25
+ /**
26
+ * Call a Twitter API POST method.
27
+ *
28
+ * @param string endpoint/method, e.g. "users/show"
29
+ * @param array Request arguments, e.g. array( 'screen_name' => 'timwhitlock' )
30
+ * @return array raw, deserialised data from Twitter
31
+ * @throws TwitterApiException
32
+ */
33
+ function twitter_api_post( $path, array $args = array() ){
34
+ $Client = twitter_api_client();
35
+ return $Client->call( $path, $args, 'POST' );
36
+ }
37
+
38
+
39
+
40
+
41
+ /**
42
+ * Enable caching of Twitter API responses using APC
43
+ * @param int Cache lifetime in seconds
44
+ * @return TwitterApiClient
45
+ */
46
+ function twitter_api_enable_cache( $ttl ){
47
+ $Client = twitter_api_client();
48
+ return $Client->enable_cache( $ttl );
49
+ }
50
+
51
+
52
+
53
+
54
+ /**
55
+ * Disable caching of Twitter API responses
56
+ * @return TwitterApiClient
57
+ */
58
+ function twitter_api_disable_cache( $ttl ){
59
+ $Client = twitter_api_client();
60
+ return $Client->disable_cache();
61
+ }
62
+
63
+
64
+
65
+
66
+ /**
67
+ * Include a component from the lib directory.
68
+ * @param string $component e.g. "core", or "admin"
69
+ * @return void fatal error on failure
70
+ */
71
+ function twitter_api_include(){
72
+ foreach( func_get_args() as $component ){
73
+ require_once twitter_api_basedir().'/lib/twitter-api-'.$component.'.php';
74
+ }
75
+ }
76
+
77
+
78
+
79
+ /**
80
+ * Get plugin local base directory in case __DIR__ isn't available (php<5.3)
81
+ */
82
+ function twitter_api_basedir(){
83
+ static $dir;
84
+ isset($dir) or $dir = dirname(__FILE__).'/..';
85
+ return $dir;
86
+ }
87
+
88
+
89
+
90
+
91
+ /**
92
+ * Get fully configured and authenticated Twitter API client.
93
+ * @return TwitterApiClient
94
+ */
95
+ function twitter_api_client( $id = null ){
96
+ static $clients = array();
97
+ if( ! isset($clients[$id]) ){
98
+ twitter_api_include('core');
99
+ $clients[$id] = TwitterApiClient::create_instance( is_null($id) );
100
+ }
101
+ return $clients[$id];
102
+ }
103
+
104
+
105
+
106
+
107
+ /**
108
+ * Contact Twitter for a request token, which will be exchanged for an access token later.
109
+ * @return TwitterOAuthToken Request token
110
+ */
111
+ function twitter_api_oauth_request_token( $consumer_key, $consumer_secret, $oauth_callback = 'oob' ){
112
+ $Client = twitter_api_client('oauth');
113
+ $Client->set_oauth( $consumer_key, $consumer_secret );
114
+ $params = $Client->oauth_exchange( TWITTER_OAUTH_REQUEST_TOKEN_URL, compact('oauth_callback') );
115
+ return new TwitterOAuthToken( $params['oauth_token'], $params['oauth_token_secret'] );
116
+ }
117
+
118
+
119
+
120
+
121
+ /**
122
+ * Exchange request token for an access token after authentication/authorization by user
123
+ * @return TwitterOAuthToken Access token
124
+ */
125
+ function twitter_api_oauth_access_token( $consumer_key, $consumer_secret, $request_key, $request_secret, $oauth_verifier ){
126
+ $Client = twitter_api_client('oauth');
127
+ $Client->set_oauth( $consumer_key, $consumer_secret, $request_key, $request_secret );
128
+ $params = $Client->oauth_exchange( TWITTER_OAUTH_ACCESS_TOKEN_URL, compact('oauth_verifier') );
129
+ return new TwitterOAuthToken( $params['oauth_token'], $params['oauth_token_secret'] );
130
+ }
131
+
132
+
133
+
134
+
135
+ // Include application settings panel if in admin area
136
+ if( is_admin() ){
137
+ twitter_api_include('core','admin');
138
+ }
readme.txt ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Plugin Name ===
2
+ Contributors: timwhitlock
3
+ Donate link: http://timwhitlock.info
4
+ Tags: twitter, tweets, oauth, api, rest
5
+ Requires at least: 3.5.1
6
+ Tested up to: 3.5.1
7
+ Stable tag: trunk
8
+ License: GPLv2 or later
9
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
+
11
+ Provides a widget showing latest tweets - compatible with the new Twitter API 1.1
12
+
13
+ == Description ==
14
+
15
+ Connect your Twitter account to this plugin and the widget will display your latest tweets on your site.
16
+
17
+ This plugin is compatible with the new Twitter API 1.1 and provides full authentication via the Wordpress admin area.
18
+
19
+
20
+ == Installation ==
21
+
22
+ 1. Unzip all files to the `/wp-content/plugins/` directory
23
+ 2. Log into Wordpress admin and activate the 'Latest Tweets' plugin through the 'Plugins' menu
24
+
25
+ Once the plugin is installed and enabled you can bind it to a Twitter account as follows:
26
+
27
+ 3. Register a Twitter application at https://dev.twitter.com/apps
28
+ 4. Note the Consumer key and Consumer secret under OAuth settings
29
+ 5. Log into Wordpress admin and go to Settings > Twitter API
30
+ 6. Enter the consumer key and secret and click 'Save settings'
31
+ 7. Click the 'Connect to Twitter' button and follow the prompts.
32
+
33
+ Once your site is authenticated you can configure the widget as follows:
34
+
35
+ 8. Log into Wordpress admin and go to Appearance > Widgets
36
+ 9. Drag 'Latest Tweets' from 'Available widgets' to where you want it. e.g. Main Sidebar
37
+ 10. Optionally configure the widget title and number of tweets to display.
38
+
39
+
40
+ == Changelog ==
41
+
42
+ = 1.0 =
43
+ * First public release
44
+