PluginProbe
Custom Twitter Feeds – A Tweets Widget or X Feed Widget / 2.7.0
Custom Twitter Feeds – A Tweets Widget or X Feed Widget v2.7.0
2.8.0 2.7.0 2.6.1 2.6.0 1.0 1.0.1 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.2 1.2.1 1.2.10 1.2.11 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 All 62 releases
custom-twitter-feeds / inc / V2 / CtfOauthConnect.php
CtfOauthConnect.php
435 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Overrides the default CtfOauthConnect class to add support for Twitter V2 endpoints.
4 */
5 namespace TwitterFeed\V2;
6
7 // Don't load directly.
8 if (! defined('ABSPATH')) {
9 die('-1');
10 }
11
12 /**
13 * CtfOAuthConnect class.
14 */
15 class CtfOauthConnect extends \TwitterFeed\CtfOauthConnect {
16 /**
17 * Username.
18 *
19 * @var string
20 */
21 protected $username;
22
23 /**
24 * Get parameters.
25 *
26 * @var string
27 */
28 protected $get_params;
29
30 /**
31 * Set base URL.
32 *
33 * @return void
34 */
35 public function setUrlBase()
36 {
37 switch ($this->feed_type) {
38 case 'userslookup':
39 $this->base_url = 'https://api.twitter.com/2/users/by/username/:username';
40 break;
41 case 'tweets':
42 $this->base_url = 'https://api.twitter.com/2/tweets';
43 break;
44 case 'accountlookup':
45 $this->base_url = 'https://api.twitter.com/1.1/account/verify_credentials.json';
46 break;
47 case 'hometimeline':
48 default:
49 $this->base_url = 'https://api.twitter.com/2/users/:id/tweets';
50 break;
51 }
52 }
53
54 /**
55 * Since the OAuth data is passed in an url, special characters need to be encoded
56 */
57 protected function encodeHeader()
58 {
59 if ( ! CTF_DOING_SMASH_TWITTER && strpos($this->base_url, '/2/') !== false ) {
60 $this->header = 'Authorization: Bearer ' . (new TwitterHelperApi($this->consumer_key, $this->consumer_secret))->getCachedTwitterAccessToken();;
61 return;
62 }
63
64 $header = 'Authorization: OAuth ';
65 $values = [];
66
67 // Each element of the header needs to have its special characters encoded for
68 // passing through a URL.
69 foreach ($this->oauth as $key => $value) {
70 if (
71 in_array(
72 $key,
73 array(
74 'oauth_consumer_key',
75 'oauth_nonce',
76 'oauth_signature',
77 'oauth_signature_method',
78 'oauth_timestamp',
79 'oauth_token',
80 'oauth_version',
81 )
82 )
83 ) {
84 $values[] = "$key=\"" . rawurlencode($value) . "\"";
85 }
86 }
87
88 $this->header = $header . implode(', ', $values);
89 }
90
91 /**
92 * Retrieve username id.
93 *
94 * @param string $username Username.
95 *
96 * @return string
97 */
98 public function retrieveUsernameId($username)
99 {
100 $cached_usernames_key = 'sb_ctf_cached_usernames';
101 $cached_usernames = get_option($cached_usernames_key, []);
102
103 if (! empty($cached_usernames[$username])) {
104 return $cached_usernames[$username];
105 }
106
107 $connect = new CtfOauthConnect([
108 'consumer_key' => $this->consumer_key,
109 'consumer_secret' => $this->consumer_secret,
110 'access_token' => $this->access_token,
111 'access_token_secret' => $this->access_token_secret,
112 ], 'userslookup');
113 $connect->setUrlBase();
114 $connect->encodeHeader();
115 $connect->setGetFields(['username' => $username]);
116 $connect->performRequest();
117
118 $user_data = json_decode($connect->json, ARRAY_A);
119
120 if (isset($user_data['data']['id'])) {
121 $username_id = $user_data['data']['id'];
122 $cached_usernames[$username] = $username_id;
123 update_option($cached_usernames_key, $cached_usernames);
124 return $username_id;
125 }
126
127 return null;
128 }
129
130 /**
131 * Perform request.
132 *
133 * @return $this|mixed|string
134 */
135 public function performRequest()
136 {
137 $url = $this->base_url . $this->get_fields;
138 if ($this->feed_type === 'hometimeline') {
139 $account_handle = get_option('ctf_options')['account_handle'];
140 $url = str_replace(':id', $this->retrieveUsernameId($account_handle), $url);
141 }
142
143 if ($this->feed_type === 'usertimeline') {
144 if (strpos($url, ':id') !== false) {
145 $account_handle = $this->username;
146 $url = str_replace(':id', $this->retrieveUsernameId($account_handle), $url);
147 }
148 }
149
150 if ($this->feed_type === 'userslookup') {
151 if (strpos($url, ':username') !== false) {
152 $url = str_replace(':username', $this->username, $url);
153 }
154 }
155
156 $this->buildOauth();
157 $this->encodeHeader();
158
159 $this->json = $this->wpHttpRequest($url);
160
161 if (in_array($this->feed_type, [ 'usertimeline', 'hometimeline', 'search', 'hashtag' ], true)) {
162 $this->json = $this->convertResponseToLegacy($this->json);
163 }
164
165 return $this;
166 }
167
168 /**
169 * Check if error.
170 *
171 * @param string $json Json.
172 *
173 * @return bool
174 */
175 public function checkIfError($json)
176 {
177 $json_array = json_decode($json, JSON_OBJECT_AS_ARRAY);
178
179 if (isset($json_array['response']['code']) && ( $json_array['response']['code'] !== 200 )) {
180 return true;
181 }
182
183 if (isset($json_array['errors'][0]['code']) && ( $json_array['errors'][0]['code'] !== 200 )) {
184 return true;
185 }
186
187 return false;
188 }
189
190 /**
191 * Encodes an array of GET field data into html characters for including in a URL
192 *
193 * @param array $get_fields array of GET fields that are compatible with the Twitter API
194 */
195 public function setGetFields(array $get_fields)
196 {
197 $joined_parameters = [];
198
199 if ($this->feed_type !== 'userslookup') {
200 $parameters = [
201 'tweet.fields' => [
202 'attachments',
203 'author_id',
204 'context_annotations',
205 'conversation_id',
206 'created_at',
207 'entities',
208 'geo',
209 'id',
210 'in_reply_to_user_id',
211 'lang',
212 'referenced_tweets',
213 'reply_settings',
214 'source',
215 'text',
216 'withheld',
217 'public_metrics',
218 ],
219 'expansions' => [
220 'author_id',
221 'attachments.media_keys',
222 'entities.mentions.username',
223 'in_reply_to_user_id',
224 'referenced_tweets.id',
225 'referenced_tweets.id.author_id',
226 ],
227 'user.fields' => [
228 'description',
229 'entities',
230 'public_metrics',
231 'profile_image_url',
232 'verified',
233 ],
234 'media.fields' => [
235 'height',
236 'media_key',
237 'preview_image_url',
238 'type',
239 'url',
240 'variants',
241 'width',
242 ],
243 ];
244
245 foreach ($parameters as $parameter_index => $parameter_values) {
246 $joined_parameters[] = $parameter_index . '=' . implode(',', $parameter_values);
247 }
248 }
249
250 if (! empty($get_fields['screen_name'])) {
251 $this->username = str_replace('@', '', $get_fields['screen_name']);
252 }
253
254 if (! empty($get_fields['username'])) {
255 $this->username = str_replace('@', '', $get_fields['username']);
256 }
257
258 if (isset($get_fields['ids'])) {
259 $joined_parameters[] = 'ids=' . rawurlencode(implode(',', $get_fields['ids']));
260 }
261
262 if (! empty($get_fields['q'])) {
263 $query = $get_fields['q'];
264
265 if (strpos($query, '-filter') !== false) {
266 $query = substr($query, 0, strpos($query, '-filter'));
267 }
268 $joined_parameters[] = 'query=' . rawurlencode($query);
269 }
270
271 if (! empty($get_fields['max_id'])) {
272 $joined_parameters[] = 'until_id=' . rawurlencode($get_fields['max_id']);
273 }
274
275 if (! empty($get_fields['count'])) {
276 $joined_parameters[] = 'max_results=' . rawurlencode(min($get_fields['count'], 100));
277 }
278
279 $this->get_fields = '?' . implode('&', $joined_parameters);
280 }
281
282 /**
283 * Convert response to legacy.
284 *
285 * @param string $json Json.
286 *
287 * @return false|string
288 */
289 public function convertResponseToLegacy($json)
290 {
291 $json_array = json_decode($json, ARRAY_A);
292
293 // Stop converting if it's an error.
294 if (! isset($json_array['data'])) {
295 return $json;
296 }
297
298 $referenced_tweets_ids = [];
299 $data_array = $json_array['data'];
300
301 foreach ($json_array['data'] as $tweet) {
302 if (empty($tweet['referenced_tweets'])) {
303 continue;
304 }
305
306 $referenced_tweets_ids = array_merge(
307 $referenced_tweets_ids,
308 array_column($tweet['referenced_tweets'], 'id')
309 );
310 }
311
312 $unique_referenced_tweets_ids = array_unique($referenced_tweets_ids);
313 $indexed_referenced_tweet_ids = [];
314 $referenced_tweets_data = [];
315
316 if (! empty($unique_referenced_tweets_ids)) {
317 $connect = new CtfOauthConnect([
318 'consumer_key' => $this->consumer_key,
319 'consumer_secret' => $this->consumer_secret,
320 'access_token' => $this->access_token,
321 'access_token_secret' => $this->access_token_secret,
322 ], 'tweets');
323 $connect->setUrlBase();
324 $connect->setGetFields([ 'ids' => $unique_referenced_tweets_ids ]);
325 $connect->performRequest();
326 $referenced_tweets_data = json_decode($connect->json, ARRAY_A);
327
328 if (
329 !empty($referenced_tweets_data['includes']['media'])
330 && is_array($referenced_tweets_data['includes']['media'])
331 ) {
332 $json_array['includes']['media'] = array_merge(
333 !empty($json_array['includes']['media'])
334 && is_array($json_array['includes']['media'])
335 ? $json_array['includes']['media']
336 : [],
337 $referenced_tweets_data['includes']['media']
338 );
339 }
340
341 if ( is_array($referenced_tweets_data['data']) ) {
342 $indexed_referenced_tweet_ids = array_column($referenced_tweets_data['data'], 'id');
343 }
344 }
345
346 $included_users = $json_array['includes']['users'];
347 $included_users_ids = array_column($included_users, 'id');
348
349 foreach ($data_array as $index => $tweet) {
350 $mentioned_user_id = isset($tweet['entities']['mentions'][0]['id']) ? $tweet['entities']['mentions'][0]['id'] : null;
351
352 $included_user_index = $mentioned_user_id ? array_search($mentioned_user_id, $included_users_ids) : null;
353
354 $referenced_tweet_type = isset($tweet['referenced_tweets'][0]['type']) ? $tweet['referenced_tweets'][0]['type'] : null;
355 $referenced_tweet_id = isset($tweet['referenced_tweets'][0]['id']) ? $tweet['referenced_tweets'][0]['id'] : null;
356
357 $media_items = isset($json_array['includes']['media']) ? (new MediaAdapter($tweet, $json_array['includes']['media']))->convert() : [];
358
359 if (in_array($referenced_tweet_type, [ 'retweeted', 'replied_to' ], true)) {
360 $referenced_tweet_index = array_search($referenced_tweet_id, $indexed_referenced_tweet_ids);
361 $retweet = $referenced_tweets_data['data'][ $referenced_tweet_index ];
362
363 // The retweeted tweet media will override the retweet media. Users cannot attach custom media to retweet.
364 $included_media = !empty($json_array['includes']['media']) && is_array($json_array['includes']['media'])
365 ? $json_array['includes']['media']
366 : [];
367 $media_items = (new MediaAdapter($retweet, $included_media))->convert();
368
369 $data_array[ $index ]['retweeted_status']['id_str'] = $referenced_tweet_id;
370 $data_array[ $index ]['retweeted_status']['text'] = $data_array[ $index ]['text'];
371 $data_array[ $index ]['retweeted_status']['created_at'] = $data_array[ $index ]['created_at'];
372 $data_array[ $index ]['retweeted_status']['extended_entities']['media'] = $media_items;
373 $data_array[ $index ]['retweeted_status']['retweet_count'] = $retweet['public_metrics']['retweet_count'];
374 $data_array[ $index ]['retweeted_status']['favorite_count'] = $retweet['public_metrics']['like_count'];
375
376 if (is_int($included_user_index)) {
377 $data_array[ $index ]['retweeted_status']['user'] = (new UserAdapter())->convert($json_array['includes']['users'][ $included_user_index ]);
378 }
379 } elseif ($referenced_tweet_type === 'quoted') {
380 $referenced_tweet_index = array_search($referenced_tweet_id, $indexed_referenced_tweet_ids);
381 $retweet = $referenced_tweets_data['data'][ $referenced_tweet_index ];
382
383 $included_media = !empty($json_array['includes']['media']) && is_array($json_array['includes']['media'])
384 ? $json_array['includes']['media']
385 : [];
386 $media_items = ! empty($media_items)
387 ? $media_items
388 : ( new MediaAdapter($retweet, $included_media) )->convert();
389
390 $retweet_author_index = array_search($retweet['author_id'], array_column($referenced_tweets_data['includes']['users'], 'id'));
391 $retweet_author_user = $referenced_tweets_data['includes']['users'][ $retweet_author_index ];
392
393 $author_index = array_search($retweet['author_id'], array_column($json_array['includes']['users'], 'id'));
394 $author_user = $json_array['includes']['users'][ $author_index ];
395
396 $data_array[ $index ]['quoted_status'] = [
397 'id_str' => $referenced_tweet_id,
398 'created_at' => $tweet['created_at'],
399 'text' => $retweet['text'],
400 'extended_entities' => [
401 'media' => $media_items,
402 ],
403 ];
404
405 if ($author_index !== false && $retweet_author_index !== false) {
406 $data_array[ $index ]['quoted_status']['user'] = (new UserAdapter())->convert(
407 array_merge(
408 $author_user,
409 $retweet_author_user
410 )
411 );
412 }
413
414 $data_array[ $index ]['retweet_count'] = $retweet['public_metrics']['retweet_count'];
415 $data_array[ $index ]['favorite_count'] = $retweet['public_metrics']['like_count'];
416 } else {
417 $data_array[ $index ]['extended_entities']['media'] = $media_items;
418 }
419
420 $data_array[ $index ]['retweet_count'] = $data_array[ $index ]['public_metrics']['retweet_count'];
421 $data_array[ $index ]['favorite_count'] = $data_array[ $index ]['public_metrics']['like_count'];
422
423 $author_id = $tweet['author_id'];
424 $user_index = array_search($author_id, $included_users_ids);
425 if (is_int($user_index)) {
426 $data_array[ $index ]['user'] = ( new UserAdapter() )->convert( $included_users[ $user_index ] );
427 }
428 $data_array[ $index ] = (new TweetAdapter())->convert($data_array[$index]);
429 }
430
431 $json_array['data'] = $data_array;
432 return wp_json_encode($json_array);
433 }
434 }
435