'GET', 'timeout' => 1, 'sslverify' => false, 'headers' => array( 'Accept-Encoding' => '*', ), ); /** * Contains the fetched response. * * @var stdClass */ protected $response; /** * Contains the error message when request went wrong. * * @var string */ protected $error_message = ''; /** * The MyYoast client object. * * @var WPSEO_MyYoast_Client */ protected $client; /** * Constructor. * * @codeCoverageIgnore * * @param string $url The request url. * @param array $args The request arguments. */ public function __construct( $url, array $args = array() ) { $this->url = 'https://my.yoast.com/api/' . $url; $this->args = wp_parse_args( $args, $this->args ); } /** * Fires the request. * * @return bool True when request is successful. */ public function fire() { try { $response = $this->do_request( $this->url, $this->args ); $this->response = $this->decode_response( $response ); return true; } /** * The Authentication exception only occurs when using Access Tokens (>= PHP 5.6). * In other case this exception won't be thrown. * * When authentication failed just try to get a new access token based * on the refresh token. If that request also has an authentication issue * we just invalidate the access token by removing it. */ catch ( WPSEO_MyYoast_Authentication_Exception $authentication_exception ) { try { $access_token = $this->get_access_token(); if ( $access_token !== false ) { $response = $this->do_request( $this->url, $this->args ); $this->response = $this->decode_response( $response ); } return true; } catch ( WPSEO_MyYoast_Authentication_Exception $authentication_exception ) { $this->error_message = $authentication_exception->getMessage(); $this->remove_access_token( $this->get_current_user_id() ); return false; } catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request_exception ) { $this->error_message = $bad_request_exception->getMessage(); return false; } } catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request_exception ) { $this->error_message = $bad_request_exception->getMessage(); return false; } } /** * Retrieves the error message. * * @return string The set error message. */ public function get_error_message() { return $this->error_message; } /** * Retrieves the response. * * @return stdClass The response object. */ public function get_response() { return $this->response; } /** * Performs the request using WordPress internals. * * @codeCoverageIgnore * * @param string $url The request URL. * @param array $request_arguments The request arguments. * * @return string The retrieved body. * @throws WPSEO_MyYoast_Authentication_Exception When authentication has failed. * @throws WPSEO_MyYoast_Bad_Request_Exception When request is invalid. */ protected function do_request( $url, $request_arguments ) { $request_arguments = $this->enrich_request_arguments( $request_arguments ); $response = wp_remote_request( $url, $request_arguments ); if ( is_wp_error( $response ) ) { throw new WPSEO_MyYoast_Bad_Request_Exception( $response->get_error_message() ); } $response_code = wp_remote_retrieve_response_code( $response ); $response_message = wp_remote_retrieve_response_message( $response ); // Do nothing, response code is okay. if ( $response_code === 200 || strpos( $response_code, '200' ) !== false ) { return wp_remote_retrieve_body( $response ); } // Authentication failed, throw an exception. if ( strpos( $response_code, '401' ) && $this->has_oauth_support() ) { throw new WPSEO_MyYoast_Authentication_Exception( esc_html( $response_message ), 401 ); } throw new WPSEO_MyYoast_Bad_Request_Exception( esc_html( $response_message ), (int) $response_code ); } /** * Decodes the JSON encoded response. * * @param string $response The response to decode. * * @return stdClass The json decoded response. * @throws WPSEO_MyYoast_Invalid_JSON_Exception When decoded string is not a JSON object. */ protected function decode_response( $response ) { $response = json_decode( $response ); if ( ! is_object( $response ) ) { throw new WPSEO_MyYoast_Invalid_JSON_Exception( esc_html__( 'No JSON object was returned.', 'wordpress-seo' ) ); } return $response; } /** * Checks if MyYoast tokens are allowed and adds the token to the request body. * * When tokens are disallowed it will add the url to the request body. * * @param array $request_arguments The arguments to enrich. * * @return array The enriched arguments. */ protected function enrich_request_arguments( array $request_arguments ) { $request_arguments = wp_parse_args( $request_arguments, array( 'headers' => array() ) ); $addon_version_headers = $this->get_installed_addon_versions(); foreach ( $addon_version_headers as $addon => $version ) { $request_arguments['headers'][ $addon . '-version' ] = $version; } $request_body = $this->get_request_body(); if ( $request_body !== array() ) { $request_arguments['body'] = $request_body; } return $request_arguments; } /** * Retrieves the request body based on URL or access token support. * * @codeCoverageIgnore * * @return array The request body. */ public function get_request_body() { if ( ! $this->has_oauth_support() ) { return array( 'url' => WPSEO_Utils::get_home_url() ); } try { $access_token = $this->get_access_token(); if ( $access_token ) { return array( 'token' => $access_token->getToken() ); } } // @codingStandardsIgnoreLine Generic.CodeAnalysis.EmptyStatement.DetectedCATCH -- There is nothing to do. catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request ) { // Do nothing. } return array(); } /** * Retrieves the access token. * * @codeCoverageIgnore * * @return bool|WPSEO_MyYoast_AccessToken_Interface The AccessToken when valid. * @throws WPSEO_MyYoast_Bad_Request_Exception When something went wrong in getting the access token. */ protected function get_access_token() { $client = $this->get_client(); if ( ! $client ) { return false; } $access_token = $client->get_access_token(); if ( ! $access_token ) { return false; } if ( ! $access_token->hasExpired() ) { return $access_token; } try { $access_token = $client ->get_provider() ->getAccessToken( 'refresh_token', array( 'refresh_token' => $access_token->getRefreshToken(), ) ); $client->save_access_token( $this->get_current_user_id(), $access_token ); return $access_token; } catch ( Exception $e ) { $error_code = $e->getCode(); if ( $error_code >= 400 && $error_code < 500 ) { $this->remove_access_token( $this->get_current_user_id() ); } throw new WPSEO_MyYoast_Bad_Request_Exception( $e->getMessage() ); } } /** * Retrieves an instance of the MyYoast client. * * @codeCoverageIgnore * * @return WPSEO_MyYoast_Client Instance of the client. */ protected function get_client() { if ( $this->client === null ) { $this->client = new WPSEO_MyYoast_Client(); } return $this->client; } /** * Wraps the get current user id function. * * @codeCoverageIgnore * * @return int The user id. */ protected function get_current_user_id() { return get_current_user_id(); } /** * Removes the access token for given user id. * * @codeCoverageIgnore * * @param int $user_id The user id. * * @return void */ protected function remove_access_token( $user_id ) { if ( ! $this->has_oauth_support() ) { return; } // Remove the access token entirely. $this->get_client()->remove_access_token( $user_id ); } /** * Retrieves the installed addons as http headers. * * @codeCoverageIgnore * * @return array The installed addon versions. */ protected function get_installed_addon_versions() { $addon_manager = new WPSEO_Addon_Manager(); return $addon_manager->get_installed_addons_versions(); } /** * Wraps the has_access_token support method. * * @codeCoverageIgnore * * @return bool False to disable the support. */ protected function has_oauth_support() { return false; // @todo: Uncomment the following statement when we are implementing the oAuth flow. // return WPSEO_Utils::has_access_token_support(); } }