PluginProbe
Gianism / trunk
Gianism vtrunk
4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.4.0 5.0.0 5.0.1 5.0.2 5.1.0 5.2.1 5.2.2 5.3.0 6.0.0 6.0.1 trunk 1.0 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 All 65 releases
gianism / app / Gianism / Service / Twitter.php

Twitter.php in Gianism trunk, at app/Gianism/Service/Twitter.php

694 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Gianism\Service;
4
5 use Abraham\TwitterOAuth\Consumer;
6 use Abraham\TwitterOAuth\HmacSha1;
7 use Abraham\TwitterOAuth\Request;
8 use Abraham\TwitterOAuth\Token;
9 use Abraham\TwitterOAuth\TwitterOAuth;
10
11 /**
12 * Description of twitter_controller
13 *
14 * @package Gianism
15 * @since 2.0.0
16 * @author Takahashi Fumiki
17 */
18 class Twitter extends NoMailService {
19
20 /**
21 * URL prefix
22 *
23 * @var string
24 */
25 public $url_prefix = 'twitter';
26
27 /**
28 * Verbose service name
29 *
30 * @var string
31 */
32 public $verbose_service_name = 'X';
33
34 /**
35 * Screen name of admin
36 *
37 * @var string
38 */
39 public $tw_screen_name = '';
40
41 /**
42 * Consumer key
43 *
44 * @var string
45 */
46 public $tw_consumer_key = '';
47
48 /**
49 * Consumer secret
50 *
51 * @var string
52 */
53 public $tw_consumer_secret = '';
54
55 /**
56 * Access token of admin
57 *
58 * @var string
59 */
60 public $tw_access_token = '';
61
62 /**
63 * Access token secret of admin
64 *
65 * @var string
66 */
67 public $tw_access_token_secret = '';
68
69 /**
70 * If use cron bot
71 *
72 * @var bool
73 */
74 public $tw_use_cron = false;
75
76 /**
77 * User's twitter id
78 *
79 * @var string
80 */
81 public $umeta_id = '_wpg_twitter_id';
82
83 /**
84 * User's screen name
85 *
86 * @var string
87 */
88 public $umeta_screen_name = '_wpg_twitter_screen_name';
89
90 /**
91 * Pseudo email address
92 *
93 * @var string
94 */
95 protected $pseudo_domain = 'pseudo.twitter.com';
96
97 /**
98 * @var TwitterOAuth
99 */
100 private $oauth = null;
101
102 /**
103 * Option key name to assign
104 *
105 * @var array
106 */
107 protected $option_keys = [
108 'tw_enabled' => false,
109 'tw_screen_name' => '',
110 'tw_consumer_key' => '',
111 'tw_consumer_secret' => '',
112 'tw_access_token' => '',
113 'tw_access_token_secret' => '',
114 'tw_use_cron' => false,
115 ];
116
117 /**
118 * Constructor
119 *
120 * @param array $argument
121 */
122 protected function __construct( array $argument = array() ) {
123 parent::__construct( $argument );
124 // TODO: Change this process if PHP requirements changed.
125 if ( version_compare( phpversion(), '5.5.0', '<' ) && $this->enabled ) {
126 add_action(
127 'admin_notices',
128 function () {
129 printf(
130 '<div class="error"><p>%s</p></div>',
131 sprintf( $this->_( 'Twitter Login requires PHP5.5 and over but yours is %s. Every twitter login will be failed.' ), phpversion() )
132 );
133 }
134 );
135 }
136 }
137
138 /**
139 * Handle callback
140 *
141 * @param string $action
142 */
143 protected function handle_default( $action ) {
144 // Get common values
145 $redirect_url = $this->session->get( 'redirect_to' );
146 /**
147 * @var array $token 'oauth_token_secret', 'oauth_token', ''
148 */
149 $token = $this->session->get( 'token' );
150 /** @var string $verifier */
151 $verifier = $this->input->get( 'oauth_verifier' );
152 // Process action
153 switch ( $action ) {
154 case 'login': // Make user login
155 try {
156 // Get information from twitter.
157 if ( ! ( $verifier ) || ! $this->validate_token( $token ) ) {
158 throw new \Exception( $this->api_error_string() );
159 }
160 $oauth = $this->get_oauth( $token['oauth_token'], $token['oauth_token_secret'] );
161 $access_token = $oauth->oauth(
162 'oauth/access_token',
163 [
164 'oauth_verifier' => $verifier,
165 ]
166 );
167 if ( ! isset( $access_token['user_id'], $access_token['screen_name'] ) ) {
168 throw new \Exception( $this->api_error_string() );
169 }
170 $twitter_id = $access_token['user_id'];
171 $screen_name = $access_token['screen_name'];
172 $oauth_token = $access_token['oauth_token'];
173 $oauth_secret = $access_token['oauth_token_secret'];
174 // Get exiting user ID
175 $user_id = $this->get_meta_owner( $this->umeta_id, $twitter_id );
176 if ( ! $user_id ) {
177 // Test
178 $this->test_user_can_register();
179 // Check if you can get email
180 $verified = $this->get_oauth( $oauth_token, $oauth_secret );
181 $profile = $verified->get(
182 'account/verify_credentials',
183 [
184 'skip_status' => 'true',
185 'include_email' => 'true',
186 ]
187 );
188 if ( $profile && isset( $profile->email ) && $profile->email ) {
189 // Yay! Email retrieved.
190 $email = $profile->email;
191 } else {
192 $email = $this->create_pseudo_email(
193 [
194 'screen_name' => $screen_name,
195 'twitter_id' => $twitter_id,
196 ]
197 );
198 }
199 // Make username from screen name
200 $user_name = ( ! username_exists( '@' . $screen_name ) ) ? '@' . $screen_name : $email;
201 /**
202 * @see Facebook
203 */
204 $user_name = apply_filters( 'gianism_register_name', $user_name, $this->service, $access_token );
205 // Create user
206 $user_id = wp_create_user( $user_name, wp_generate_password(), $email );
207 if ( is_wp_error( $user_id ) ) {
208 throw new \Exception( $this->registration_error_string() );
209 }
210 // Update extra information
211 update_user_meta( $user_id, $this->umeta_id, $twitter_id );
212 update_user_meta( $user_id, $this->umeta_screen_name, $screen_name );
213 update_user_meta( $user_id, 'nickname', '@' . $screen_name );
214 $this->db->update(
215 $this->db->users,
216 [
217 'display_name' => "@{$screen_name}",
218 'user_url' => 'https://twitter.com/' . $screen_name,
219 ],
220 [
221 'ID' => $user_id,
222 ],
223 [ '%s', '%s' ],
224 [ '%d' ]
225 );
226 // Password is unknown
227 $this->user_password_unknown( $user_id );
228 $this->hook_connect( $user_id, $oauth, true );
229 // Let user follow me
230 $this->welcome( '@' . $screen_name );
231 }
232 // Let user log in.
233 $this->set_auth_cookie( $user_id );
234 $redirect_url = $this->filter_redirect( $redirect_url, 'login' );
235 } catch ( \Exception $e ) {
236 $this->auth_fail( $e->getMessage() );
237 $redirect_url = $this->filter_redirect( wp_login_url( $redirect_url, true ), 'login-failure' );
238 }
239 wp_redirect( $redirect_url );
240 exit;
241 break;
242 case 'connect':
243 // Connection finished. Let's redirect.
244 if ( ! $redirect_url ) {
245 $redirect_url = admin_url( 'profile.php' );
246 }
247 try {
248 // Is user logged in?
249 if ( ! is_user_logged_in() ) {
250 throw new \Exception( $this->_( 'You must be logged in.' ) );
251 }
252 // Get information from twitter.
253 if ( ! ( $verifier ) || ! $this->validate_token( $token ) ) {
254 throw new \Exception( $this->api_error_string() );
255 }
256 // Get user
257 $oauth = $this->get_oauth( $token['oauth_token'], $token['oauth_token_secret'] );
258 $access_token = $oauth->oauth(
259 'oauth/access_token',
260 [
261 'oauth_verifier' => $verifier,
262 ]
263 );
264 if ( ! isset( $access_token['user_id'], $access_token['screen_name'] ) ) {
265 throw new \Exception( $this->api_error_string() );
266 }
267 $twitter_id = $access_token['user_id'];
268 $screen_name = $access_token['screen_name'];
269 // Check if other user has registered
270 $id_owner = $this->get_meta_owner( $this->umeta_id, $twitter_id );
271 if ( $id_owner && ( get_current_user_id() !== $id_owner ) ) {
272 throw new \Exception( $this->duplicate_account_string() );
273 }
274 // O.K.
275 update_user_meta( get_current_user_id(), $this->umeta_id, $twitter_id );
276 update_user_meta( get_current_user_id(), $this->umeta_screen_name, $screen_name );
277 $this->hook_connect( get_current_user_id(), $oauth, false );
278 $this->welcome( '@' . $screen_name );
279 $redirect_url = $this->filter_redirect( $redirect_url, 'connect' );
280 } catch ( \Exception $e ) {
281 $this->auth_fail( $e->getMessage() );
282 $redirect_url = $this->filter_redirect( $redirect_url, 'connect-failure' );
283 }
284 wp_redirect( $redirect_url );
285 exit;
286 default:
287 /**
288 * @see Facebook
289 */
290 do_action(
291 'gianism_extra_action',
292 $this->service_name,
293 $action,
294 [
295 'redirect_to' => $redirect_url,
296 ]
297 );
298 $this->input->wp_die( sprintf( $this->_( 'Sorry, but wrong access. Please go back to <a href="%s">%s</a>.' ), home_url( '/' ), get_bloginfo( 'name' ) ), 500, false );
299 break;
300 }
301 }
302
303 /**
304 * Returns whether the user has a twitter account.
305 *
306 * @param int $user_id
307 *
308 * @return boolean
309 */
310 public function is_connected( $user_id ) {
311 return (bool) get_user_meta( $user_id, $this->umeta_id, true );
312 }
313
314 /**
315 * Disconnect user from this service
316 *
317 * @param int $user_id
318 *
319 * @return mixed|void
320 */
321 public function disconnect( $user_id ) {
322 delete_user_meta( $user_id, $this->umeta_id );
323 delete_user_meta( $user_id, $this->umeta_screen_name );
324 }
325
326 /**
327 * Returns API endpoint
328 *
329 * @param string $action
330 *
331 * @return false|string
332 * @throws \Exception
333 */
334 protected function get_api_url( $action ) {
335 switch ( $action ) {
336 case 'connect':
337 case 'login':
338 $oauth = $this->get_oauth();
339 $token = $oauth->oauth(
340 'oauth/request_token',
341 [
342 'oauth_callback' => $this->get_redirect_endpoint(),
343 ]
344 );
345 if ( ! $this->validate_token( $token ) ) {
346 throw new \Exception( $this->api_error_string() );
347 }
348 $url = $this->authorize_url( $token );
349 if ( $url ) {
350 $this->session->write( 'token', $token );
351 return $url;
352 } else {
353 return false;
354 }
355 break;
356 default:
357 return false;
358 break;
359 }
360 }
361
362 /**
363 * Get pseudo email
364 *
365 * @param mixed $prefix
366 *
367 * @return string
368 */
369 protected function create_pseudo_email( $prefix ) {
370 // No mail, let's make pseudo mail address.
371 $email = $prefix['screen_name'] . '@' . $this->pseudo_domain;
372 if ( email_exists( $email ) ) {
373 $email = 'tw-' . $prefix['twitter_id'] . '@' . $this->pseudo_domain;
374 }
375 return $email;
376 }
377
378 /**
379 * Authorize URL
380 *
381 * @param array $token
382 * @return string
383 */
384 private function authorize_url( $token ) {
385 return 'https://api.twitter.com/oauth/authorize?oauth_token=' . $token['oauth_token'];
386 }
387
388 /**
389 * Validate token
390 *
391 * @param mixed $token
392 * @param bool $url_confirmed If redirect URL must be validated
393 *
394 * @return bool
395 */
396 private function validate_token( $token, $url_confirmed = true ) {
397 if ( ! is_array( $token ) || empty( $token ) ) {
398 return false;
399 }
400 if ( ! isset( $token['oauth_token'], $token['oauth_token_secret'] ) ) {
401 return false;
402 }
403 if ( $url_confirmed && ( ! isset( $token['oauth_callback_confirmed'] ) || ! $token['oauth_callback_confirmed'] ) ) {
404 return false;
405 }
406
407 return true;
408 }
409
410 /**
411 * Get API wrapper
412 *
413 * @param string $oauth_token
414 * @param string $oauth_token_secret
415 *
416 * @return TwitterOAuth
417 */
418 public function get_oauth( $oauth_token = null, $oauth_token_secret = null ) {
419 $oauth = new TwitterOAuth( $this->tw_consumer_key, $this->tw_consumer_secret, $oauth_token, $oauth_token_secret );
420 /**
421 * Twitter OAuth Client
422 *
423 * @filter gianism_twitter_oauth_client
424 * @param TwitterOauth $oauth
425 * @param string $oauth_token
426 * @param string $oauth_token_secret
427 * @return TwitterOAuth
428 */
429 $oauth = apply_filters( 'gianism_twitter_oauth_client', $oauth, $oauth_token, $oauth_token_secret );
430 return $oauth;
431 }
432
433 /**
434 * Tweet with Owner ID
435 *
436 * @see https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets
437 *
438 * @param string $text
439 * @param null $deprecated Since 5.1.0
440 * @param array $options
441 * @param string $token
442 * @param string $secret
443 *
444 * @return object|\WP_Error Json format object.
445 */
446 public function tweet( $text, $deprecated = null, array $options = [], $token = '', $secret = '' ) {
447 return $this->call_api(
448 'tweets',
449 array_merge(
450 [
451 'text' => $text,
452 ],
453 $options
454 ),
455 'POST',
456 $deprecated,
457 $token,
458 $secret
459 );
460 }
461
462 /**
463 * Tweet with media
464 *
465 * @param string $text
466 * @param array $medias
467 * @param TwitterOAuth $oauth
468 * @param string $token
469 * @param string $secret
470 *
471 * @return object|\WP_Error JSON format object
472 */
473 public function tweet_with_media( $text, array $medias, $oauth = null, $token = '', $secret = '' ) {
474 $media_ids = [];
475 foreach ( $medias as $media ) {
476 $media_id = $this->upload( $media, $oauth );
477 if ( ! is_wp_error( $media_id ) ) {
478 $media_ids[] = $media_id;
479 }
480 }
481 if ( ! $media_ids ) {
482 return new \WP_Error( 500, __( 'Failed to upload media', 'wp-gianism' ) );
483 }
484 return $this->tweet(
485 $text,
486 $oauth,
487 [
488 'media' => [
489 'media_ids' => $media_ids,
490 ],
491 ],
492 $token,
493 $secret
494 );
495 }
496
497 /**
498 * Upload media to twitter.
499 *
500 * @since 3.0.7
501 * @param int|string $path_or_id attachment ID or URL. Integer is recognized as attachment ID.
502 * @param TwitterOAuth $oauth Default null.
503 * @return string|\WP_Error Media ID on twitter.
504 */
505 public function upload( $path_or_id, $oauth = null ) {
506 if ( is_null( $oauth ) ) {
507 $oauth = $this->get_oauth( $this->tw_access_token, $this->tw_access_token_secret );
508 }
509 if ( is_numeric( $path_or_id ) ) {
510 // This is attachment
511 $path = get_attached_file( $path_or_id );
512 if ( ! $path ) {
513 return new \WP_Error( 404, __( 'File not found.', 'wp-gianism' ) );
514 }
515 $object = $path;
516 } elseif ( preg_match( '#^https?://#u', $path_or_id ) ) {
517 // This is URL
518 $file = @file_get_contents( $path_or_id );
519 if ( ! $file ) {
520 return new \WP_Error( 404, __( 'File not found.', 'wp-gianism' ) );
521 }
522 $path = sys_get_temp_dir() . '/' . tmpfile() . '-' . basename( $path_or_id );
523 if ( ! file_put_contents( $path, $file ) ) {
524 return new \WP_Error( 500, __( 'Failed to download media', 'wp-gianism' ) );
525 }
526 $object = $path;
527 } else {
528 // This is file.
529 if ( ! file_exists( $path_or_id ) ) {
530 return new \WP_Error( 404, __( 'File not found.', 'wp-gianism' ) );
531 }
532 $object = $path_or_id;
533 }
534 $media = $oauth->upload(
535 'media/upload',
536 [
537 'media' => $object,
538 ]
539 );
540 if ( ! $media ) {
541 return new \WP_Error( 500, __( 'Failed to upload media to twitter.', 'wp-gianism' ) );
542 }
543 /** @var \stdClass $media */
544 return $media->media_id_string;
545 }
546
547 /**
548 * Send direct message on twitter.
549 *
550 * @param int $user_id
551 * @param string $text
552 * @param TwitterOAuth $oauth
553 * @deprecated 5.1.0
554 *
555 * @return object|\WP_Error
556 */
557 public function send_dm( $user_id, $text, $oauth = null ) {
558 return new \WP_Error( 'twitter_api_error', __( 'twitter DM is deprecated.', 'wp-gianism' ) );
559 }
560
561 /**
562 * Force authenticated user to follow me
563 *
564 * @param TwitterOAuth $oauth
565 * @param string $screen_name
566 * @deprecated 5.1.0
567 *
568 * @return object|\WP_Error Json format object.
569 */
570 private function follow_me( TwitterOAuth $oauth = null, $screen_name = '' ) {
571 return new \WP_Error( 'twitter_api_error', __( 'Follow API is deprecated.', 'wp-gianism' ) );
572 }
573
574 /**
575 * Get mentions
576 *
577 * @param array $args
578 * @param TwitterOAuth $oauth
579 *
580 * @return object|\WP_Error
581 */
582 public function get_mentions( $args = array(), $oauth = null ) {
583 $args = wp_parse_args(
584 $args,
585 array(
586 'count' => 20,
587 'since_id' => false,
588 'max_id' => false,
589 )
590 );
591 $args['count'] = max( 20, min( 200, $args['count'] ) );
592 foreach ( array( 'since_id', 'max_id' ) as $key ) {
593 if ( ! $args[ $key ] ) {
594 unset( $args[ $key ] );
595 }
596 }
597
598 return $this->call_api( 'statuses/mentions_timeline', $args, 'GET', $oauth );
599 }
600
601 /**
602 * Returns GET api request.
603 *
604 * You should know what kind of APIs are available.
605 *
606 * @see https://dev.twitter.com/docs/api/1.1
607 * @since 5.2.0 Change twitter api v1.1 to v2
608 *
609 * @param string $endpoint API URL. Must not be started with slash. i.e. 'statuses/user_timeline'
610 * @param array $data Data to send to API.
611 * @param string $method GET, POST, PUT, or DELETE. Default GET
612 * @param null $deprecated Formerly used for $token. No longer used.
613 * @param string $access_token If set, use this token.
614 * @param string $token_secret If set, use this token secret.
615 *
616 * @return object|\WP_Error Maybe JSON object.
617 */
618 public function call_api( $endpoint, array $data, $method = 'GET', $deprecated = null, $access_token = '', $token_secret = '' ) {
619 $method = strtoupper( $method );
620 $consumer = new Consumer( $this->tw_consumer_key, $this->tw_consumer_secret );
621 $token = new Token(
622 $access_token ?: $this->tw_access_token,
623 $token_secret ?: $this->tw_access_token_secret
624 );
625 // Only post allow json payload.
626 $json = 'POST' === $method;
627 $url = sprintf( 'https://api.twitter.com/2/%s', ltrim( $endpoint, '/' ) );
628 // Create request object.
629 $request = Request::fromConsumerAndToken( $consumer, $token, $method, $url, $data, $json );
630 if ( array_key_exists( 'oauth_callback', $data ) ) {
631 unset( $data['oauth_callback'] );
632 }
633 // Create request.
634 $request->signRequest( new HmacSha1(), $consumer, $token );
635 $authorization = $request->toHeader();
636 if ( array_key_exists( 'oauth_verifier', $data ) ) {
637 unset( $data['oauth_verifier'] );
638 }
639 $arguments = [
640 'method' => $method,
641 ];
642 $headers = [
643 'Accept' => 'application/json',
644 'Authorization' => str_replace( 'Authorization: ', '', $authorization ),
645 ];
646 if ( 'POST' === $method ) {
647 $headers['Content-Type'] = 'application/json';
648 $arguments['body'] = json_encode( $data );
649 } elseif ( ! empty( $data ) ) {
650 foreach ( $data as $key => $value ) {
651 $data[ $key ] = rawurlencode( $value );
652 }
653 $url = add_query_arg( $data, $url );
654 }
655 $arguments['headers'] = $headers;
656 $response = wp_remote_request( $url, $arguments );
657 if ( is_wp_error( $response ) ) {
658 return $response;
659 }
660 $status = $response['response']['code'];
661 if ( preg_match( '/[45]\d{2}/u', $status ) ) {
662 return new \WP_Error(
663 'twitter_api_error',
664 sprintf( '%s: %s', $response['response']['code'], $response['response']['message'] ),
665 $response
666 );
667 }
668 $body = json_decode( wp_remote_retrieve_body( $response ) );
669 if ( is_null( $body ) ) {
670 return new \WP_Error( 'twitter_api_error', __( 'Failed to parse API response.', 'wp-gianism' ) );
671 }
672 $errors = new \WP_Error();
673 if ( ! empty( $body->errors ) ) {
674 foreach ( $body->errors as $error ) {
675 $errors->add(
676 'twitter_api_error',
677 $error->message,
678 [
679 'code' => $error->code,
680 ]
681 );
682 }
683 }
684 return ! $errors->get_error_codes() ? $body : $errors;
685 }
686
687 /**
688 * {@inheritdoc}
689 */
690 protected function svg_path() {
691 return 'x.svg';
692 }
693 }
694