PluginProbe
Social Media Auto Poster – Schedule & Publish to Buffer / trunk
Social Media Auto Poster – Schedule & Publish to Buffer vtrunk
6.2.4 6.2.3 6.2.2 6.2.1 6.2.0 6.1.2 6.1.1 6.1.0 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 6.0.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 All 125 releases
← All changes | includes/class-buffer-api.php +128 -87 6.2.2trunk View file →
@@ -84,9 +84,9 @@
84 84 * Token Expiry Timestamp
85 85 *
86 86 * @since 3.5.0
87 87 *
88 - * @var int|false
88 + * @var int|bool
89 89 */
90 90 public $token_expires = false;
91 91
92 92 /**
@@ -116,9 +116,9 @@
116 116 ?>
117 117 <div class="wpzinc-option">
118 118 <div class="full">
119 119 <a href="<?php echo esc_attr( $this->get_oauth_url() ); ?>" class="button button-primary">
120 - <?php esc_html_e( 'Connect a Buffer Account', 'wp-to-buffer' ); ?>
120 + <?php esc_html_e( 'Connect an additional Buffer Account', 'wp-to-buffer' ); ?>
121 121 </a>
122 122 </div>
123 123 </div>
124 124 <?php
@@ -331,8 +331,13 @@
331 331 * @param bool|int $token_expires Token Expiry.
332 332 */
333 333 public function set_tokens( $access_token = '', $refresh_token = '', $token_expires = false ) {
334 334
335 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: set_tokens(): Started.' );
336 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: set_tokens(): access_token = ' . $access_token );
337 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: set_tokens(): refresh_token = ' . $refresh_token );
338 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: set_tokens(): token_expires = ' . $token_expires );
339 +
335 340 $this->access_token = $access_token;
336 341 $this->refresh_token = $refresh_token;
337 342 $this->token_expires = $token_expires;
338 343
@@ -388,18 +393,30 @@
388 393 * @since 2.0.0
389 394 */
390 395 public function refresh_token() {
391 396
392 - // Bail if no refresh token is available to use, otherwise we'll
393 - // send a request to Buffer with an empty refresh_token, which
394 - // will fail.
397 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): Started.' );
398 +
399 + // Bail if we don't have a refresh token.
395 400 if ( empty( $this->refresh_token ) ) {
396 - return new \WP_Error(
397 - $this->base->plugin->filter_name . '_api_refresh_token_error',
398 - __( 'No refresh token available; cannot refresh access token.', 'wp-to-buffer' )
399 - );
401 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): Error: No refresh token available; cannot refresh.' );
402 + return new \WP_Error( 'missing_refresh_token', __( 'No refresh token exists', 'wp-to-buffer' ) );
400 403 }
401 404
405 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): access_token = ' . $this->access_token );
406 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): refresh_token = ' . $this->refresh_token );
407 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): token_expires = ' . $this->token_expires );
408 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): token_expires = ' . ( $this->token_expires && time() > (int) $this->token_expires ? 'expired ' . ( time() - (int) $this->token_expires ) . 's ago' : 'expires in ' . ( (int) $this->token_expires - time() ) . 's' ) );
409 +
410 + // Bail if the access token hasn't yet expired.
411 + if ( strtotime( '+15 minutes' ) < $this->token_expires ) {
412 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): Skipped: Access token not yet within the 15 minute refresh window.' );
413 + return false;
414 + }
415 +
416 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): Requesting new access token.' );
417 +
418 + // Send request.
402 419 $result = $this->oauth_request(
403 420 $this->oauth_authorize_url . 'token',
404 421 array(
405 422 'client_id' => $this->client_id,
@@ -409,14 +426,16 @@
409 426 );
410 427
411 428 // If an error occured, log and return it now.
412 429 if ( is_wp_error( $result ) ) {
430 + $this->base->get_class( 'log' )->add_to_debug_log( sprintf( 'Buffer API: refresh_token(): Error: [%s] %s', $result->get_error_code(), $result->get_error_message() ) );
431 +
413 432 /**
414 433 * Perform any actions when refreshing an expired access token fails.
415 434 *
416 435 * @since 6.0.0
417 436 *
418 - * @param \WP_Error $result Error from API.
437 + * @param \WP_Error $result Error from API.
419 438 * @param string $client_id OAuth Client ID.
420 439 * @param string $access_token Access Token.
421 440 * @param string $refresh_token Refresh Token.
422 441 */
@@ -431,8 +450,14 @@
431 450 'refresh_token' => $result['refresh_token'],
432 451 'token_expires' => strtotime( '+' . $result['expires_in'] . ' seconds' ),
433 452 );
434 453
454 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): Success' );
455 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): New access_token = ' . $result['access_token'] );
456 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): New refresh_token = ' . $result['refresh_token'] );
457 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): New token_expires = ' . $result['token_expires'] );
458 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: refresh_token(): New token_expires = ' . ( $result['token_expires'] && time() > (int) $result['token_expires'] ? 'expired ' . ( time() - (int) $result['token_expires'] ) . 's ago' : 'expires in ' . ( (int) $result['token_expires'] - time() ) . 's' ) );
459 +
435 460 /**
436 461 * Perform any actions with the new access token, such as saving it.
437 462 *
438 463 * @since 6.0.0
@@ -559,11 +584,14 @@
559 584 // Return stored profiles if available and not forcing a refresh.
560 585 $option_name = $this->base->plugin->name . '-profiles-' . $account_id;
561 586 $profiles = get_option( $option_name );
562 587 if ( ! $force && is_array( $profiles ) ) {
588 + $this->base->get_class( 'log' )->add_to_debug_log( sprintf( 'Buffer API: profiles(): account=%s: returning %d cached profile(s).', $account_id, count( $profiles ) ) );
563 589 return $profiles;
564 590 }
565 591
592 + $this->base->get_class( 'log' )->add_to_debug_log( sprintf( 'Buffer API: profiles(): account=%s: fetching profiles from Buffer (force=%s).', $account_id, $force ? 'yes' : 'no' ) );
593 +
566 594 // Build GraphQL query.
567 595 $query = '
568 596 query GetChannels($organizationId: OrganizationId!) {
569 597 channels(input: { organizationId: $organizationId }) {
@@ -594,8 +622,9 @@
594 622 );
595 623
596 624 // Check for errors.
597 625 if ( is_wp_error( $results ) ) {
626 + $this->base->get_class( 'log' )->add_to_debug_log( sprintf( 'Buffer API: profiles(): account=%s: FAILED to fetch profiles: [%s] %s', $account_id, $results->get_error_code(), $results->get_error_message() ) );
598 627 return $results;
599 628 }
600 629
601 630 // Build profiles array from results.
@@ -742,9 +771,9 @@
742 771 switch ( $service ) {
743 772
744 773 case 'instagram':
745 774 $metadata = array(
746 - 'type' => $params['post_type'] === 'story' ? 'story' : 'post',
775 + 'type' => in_array( $params['post_type'], array( 'story', 'video_story' ), true ) ? 'story' : 'post',
747 776 'shouldShareToFeed' => true,
748 777 );
749 778
750 779 // First Comment.
@@ -750,8 +779,13 @@
750 779 // First Comment.
751 780 if ( ! empty( $params['first_comment'] ) ) {
752 781 $metadata['firstComment'] = $params['first_comment'];
753 782 }
783 +
784 + // Shop Grid Link.
785 + if ( ! empty( $params['url'] ) ) {
786 + $metadata['link'] = $params['url'];
787 + }
754 788 break;
755 789
756 790 case 'facebook':
757 791 $metadata = array(
@@ -764,14 +798,10 @@
764 798 }
765 799
766 800 // OpenGraph / Link Attachment.
767 801 if ( $params['post_type'] === 'link' && ! empty( $params['url'] ) ) {
768 - $assets = array(
769 - array(
770 - 'link' => array(
771 - 'url' => $params['url'],
772 - ),
773 - ),
802 + $metadata['linkAttachment'] = array(
803 + 'url' => $params['url'],
774 804 );
775 805 }
776 806
777 807 // Annotations.
@@ -787,14 +817,10 @@
787 817 }
788 818
789 819 // OpenGraph / Link Attachment.
790 820 if ( $params['post_type'] === 'link' && ! empty( $params['url'] ) ) {
791 - $assets = array(
792 - array(
793 - 'link' => array(
794 - 'url' => $params['url'],
795 - ),
796 - ),
821 + $metadata['linkAttachment'] = array(
822 + 'url' => $params['url'],
797 823 );
798 824 }
799 825
800 826 // Annotations.
@@ -805,13 +831,11 @@
805 831
806 832 case 'twitter':
807 833 // First Comment.
808 834 if ( ! empty( $params['first_comment'] ) ) {
809 - $metadata = array(
810 - 'thread' => array(
811 - array(
812 - 'text' => $params['first_comment'],
813 - ),
835 + $metadata['thread'] = array(
836 + array(
837 + 'text' => $params['first_comment'],
814 838 ),
815 839 );
816 840 }
817 841
@@ -869,24 +893,18 @@
869 893
870 894 case 'threads':
871 895 // OpenGraph / Link Attachment.
872 896 if ( $params['post_type'] === 'link' && ! empty( $params['url'] ) ) {
873 - $assets = array(
874 - array(
875 - 'link' => array(
876 - 'url' => $params['url'],
877 - ),
878 - ),
897 + $metadata['linkAttachment'] = array(
898 + 'url' => $params['url'],
879 899 );
880 900 }
881 901
882 902 // First Comment.
883 903 if ( ! empty( $params['first_comment'] ) ) {
884 - $metadata = array(
885 - 'thread' => array(
886 - array(
887 - 'text' => $params['first_comment'],
888 - ),
904 + $metadata['thread'] = array(
905 + array(
906 + 'text' => $params['first_comment'],
889 907 ),
890 908 );
891 909 }
892 910 break;
@@ -893,24 +911,18 @@
893 911
894 912 case 'bluesky':
895 913 // OpenGraph / Link Attachment.
896 914 if ( $params['post_type'] === 'link' && ! empty( $params['url'] ) ) {
897 - $assets = array(
898 - array(
899 - 'link' => array(
900 - 'url' => $params['url'],
901 - ),
902 - ),
915 + $metadata['linkAttachment'] = array(
916 + 'url' => $params['url'],
903 917 );
904 918 }
905 919
906 920 // First Comment.
907 921 if ( ! empty( $params['first_comment'] ) ) {
908 - $metadata = array(
909 - 'thread' => array(
910 - array(
911 - 'text' => $params['first_comment'],
912 - ),
922 + $metadata['thread'] = array(
923 + array(
924 + 'text' => $params['first_comment'],
913 925 ),
914 926 );
915 927 }
916 928 break;
@@ -917,13 +929,11 @@
917 929
918 930 case 'mastodon':
919 931 // First Comment.
920 932 if ( ! empty( $params['first_comment'] ) ) {
921 - $metadata = array(
922 - 'thread' => array(
923 - array(
924 - 'text' => $params['first_comment'],
925 - ),
933 + $metadata['thread'] = array(
934 + array(
935 + 'text' => $params['first_comment'],
926 936 ),
927 937 );
928 938 }
929 939
@@ -964,18 +974,31 @@
964 974 'image' => array(
965 975 'url' => $media['image'],
966 976 'thumbnailUrl' => $media['thumbnail'],
967 977 'metadata' => array(
968 - 'altText' => $media['alt_text'],
969 - 'dimensions' => array(
970 - 'width' => $media['width'],
971 - 'height' => $media['height'],
972 - ),
978 + 'altText' => $media['alt_text'],
973 979 ),
974 980 ),
975 981 );
976 982 }
977 983 break;
984 +
985 + case 'video':
986 + case 'video_story':
987 + // Bail if no video URL is defined.
988 + if ( empty( $params['video_url'] ) ) {
989 + break;
990 + }
991 +
992 + // Buffer fetches the video from a public URL, so we only send the URL.
993 + $assets = array(
994 + array(
995 + 'video' => array(
996 + 'url' => $params['video_url'],
997 + ),
998 + ),
999 + );
1000 + break;
978 1001 }
979 1002
980 1003 // Include assets. Always overwrites the default empty array
981 1004 // initialised above, if the service branch built any.
@@ -1091,13 +1114,40 @@
1091 1114 * @since 6.0.0
1092 1115 *
1093 1116 * @param string $query GraphQL Query.
1094 1117 * @param array $variables GraphQL Variables.
1095 - * @param bool $is_retry Whether this is a retry following a token refresh.
1096 1118 * @return \WP_Error|array
1097 1119 */
1098 - private function graphql_query( $query, $variables = array(), $is_retry = false ) {
1120 + private function graphql_query( $query, $variables = array() ) {
1099 1121
1122 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): Started.' );
1123 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): query = ' . $query );
1124 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): variables = ' . print_r( $variables, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1125 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): access_token = ' . $this->access_token );
1126 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): refresh_token = ' . $this->refresh_token );
1127 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): token_expires = ' . $this->token_expires );
1128 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): token_expires = ' . ( $this->token_expires && time() > (int) $this->token_expires ? 'expired ' . ( time() - (int) $this->token_expires ) . 's ago' : 'expires in ' . ( (int) $this->token_expires - time() ) . 's' ) );
1129 +
1130 + // Check required parameters exist.
1131 + if ( empty( $this->access_token ) ) {
1132 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): Error: No access token was specified' );
1133 + return new \WP_Error( 'missing_access_token', __( 'No access token was specified', 'wp-to-buffer' ) );
1134 + }
1135 +
1136 + // Fetch a new access token and refresh token.
1137 + $result = $this->refresh_token();
1138 +
1139 + // Bail if something went wrong.
1140 + if ( is_wp_error( $result ) ) {
1141 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): Error: ' . $result->get_error_message() );
1142 + return $result;
1143 + }
1144 +
1145 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): access_token = ' . $this->access_token );
1146 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): refresh_token = ' . $this->refresh_token );
1147 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): token_expires = ' . $this->token_expires );
1148 + $this->base->get_class( 'log' )->add_to_debug_log( 'Buffer API: graphql_query(): token_expires = ' . ( $this->token_expires && time() > (int) $this->token_expires ? 'expired ' . ( time() - (int) $this->token_expires ) . 's ago' : 'expires in ' . ( (int) $this->token_expires - time() ) . 's' ) );
1149 +
1100 1150 // Build body.
1101 1151 $body = array( 'query' => $query );
1102 1152 if ( ! empty( $variables ) ) {
1103 1153 $body['variables'] = $variables;
@@ -1115,41 +1165,22 @@
1115 1165 );
1116 1166
1117 1167 // If an error occured, return it now.
1118 1168 if ( is_wp_error( $result ) ) {
1169 + $this->base->get_class( 'log' )->add_to_debug_log( sprintf( 'Buffer API: graphql_query(): Error: [%s] %s', $result->get_error_code(), $result->get_error_message() ) );
1119 1170 return $result;
1120 1171 }
1121 1172
1122 - // Parse result.
1173 + // Parse the response.
1123 1174 $response = $this->parse_response( $result );
1124 1175
1125 - // If this is a retry, return the parsed response.
1126 - // This prevents an infinite loop of retries when a token refresh fails.
1127 - if ( $is_retry ) {
1128 - return $response;
1176 + // Log any API error, so failures that would otherwise be silent are captured.
1177 + if ( is_wp_error( $response ) ) {
1178 + $this->base->get_class( 'log' )->add_to_debug_log( sprintf( 'Buffer API: graphql_query(): API returned error: [%s] %s', $response->get_error_code(), $response->get_error_message() ) );
1129 1179 }
1130 1180
1131 - // If the request was successful, return the response.
1132 - if ( ! is_wp_error( $response ) ) {
1133 - return $response;
1134 - }
1181 + return $response;
1135 1182
1136 - // If the error isn't an unauthenticated error, return it.
1137 - if ( strtolower( $response->get_error_code() ) !== 'unauthenticated' ) {
1138 - return $response;
1139 - }
1140 -
1141 - // Attempt to refresh the token.
1142 - $refresh_result = $this->refresh_token();
1143 -
1144 - // Bail if the refresh token attempt failed.
1145 - if ( is_wp_error( $refresh_result ) ) {
1146 - return $refresh_result;
1147 - }
1148 -
1149 - // Attempt the request again, now we have a new access token.
1150 - return $this->graphql_query( $query, $variables, true );
1151 -
1152 1183 }
1153 1184
1154 1185 /**
1155 1186 * Returns the headers to use in an authenticated GraphQL API request.
@@ -1259,10 +1290,20 @@
1259 1290 '403 Forbidden'
1260 1291 );
1261 1292 }
1262 1293
1294 + // Retain-and-retry on server errors: return without touching stored tokens.
1295 + if ( $http_code >= 500 ) {
1296 + return new \WP_Error( 'buffer_api_server_error', $http_code . ' server error' );
1297 + }
1298 +
1263 1299 // Decode response.
1264 1300 $body = json_decode( $http_body, true );
1301 +
1302 + // Bail if the response isn't valid JSON.
1303 + if ( ! is_array( $body ) ) {
1304 + return new \WP_Error( 'buffer_api_invalid_response', 'Invalid API response' );
1305 + }
1265 1306
1266 1307 // If an error is detected, return it.
1267 1308 if ( array_key_exists( 'error', $body ) ) {
1268 1309 return new \WP_Error(