Version Description
Download this release
Release Info
Developer | timwhitlock |
Plugin | Latest Tweets Widget |
Version | 1.0.0 |
Comparing to | |
See all releases |
Version 1.0.0
- lang/README.md +5 -0
- lang/twitter-api.pot +270 -0
- latest-tweets.php +123 -0
- lib/twitter-api-admin.php +198 -0
- lib/twitter-api-core.php +604 -0
- lib/twitter-api-utils.php +93 -0
- lib/twitter-api.php +138 -0
- readme.txt +44 -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> </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&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 |
+
|