Yoast\WP\SEO\MyYoast_Client\Application
MyYoast_Client{} │ Yoast 1.0└─ LoggerAwareInterface
Primary facade for the MyYoast OAuth client.
Orchestrates registration, token lifecycle, and authenticated requests. This is the main entry point for consuming code.
Хуков нет.
Использование
$MyYoast_Client = new MyYoast_Client(); // use class methods
Методы
- public __construct(
- public authenticated_request( string $method, string $url, Token_Set $token_set, array $options = [] )
- public clear_site_token()
- public deregister()
- public ensure_registered( array $redirect_uris = [] )
- public exchange_authorization_code( int $user_id, string $code, string $state )
- public get_authorization_url( int $user_id, string $redirect_uri, array $scopes = [], ?string $return_url = null )
- public get_site_token( array $scopes = [] )
- public get_user_token( int $user_id, array $required_scopes = [] )
- public has_user_token( int $user_id )
- public is_registered( array $redirect_uris = [] )
- public revoke_token(
- public revoke_user_token( int $user_id )
- public rotate_dpop_keys()
- public rotate_registration_keys()
- public verify_registration()
Код MyYoast_Client{} MyYoast Client{} Yoast 27.7
class MyYoast_Client implements LoggerAwareInterface {
use LoggerAwareTrait;
private const REFRESH_LOCK_TTL_IN_SECONDS = 30;
/**
* The client registration port.
*
* @var Client_Registration_Interface
*/
private $client_registration;
/**
* The authorization code handler.
*
* @var Authorization_Code_Handler
*/
private $auth_code_handler;
/**
* The OAuth grant handler.
*
* @var OAuth_Grant_Handler
*/
private $grant_handler;
/**
* The token revocation handler.
*
* @var Token_Revocation_Handler
*/
private $revocation_handler;
/**
* The OAuth server client port.
*
* @var OAuth_Server_Client_Interface
*/
private $http_client;
/**
* The lock helper.
*
* @var Lock_Helper
*/
private $lock_helper;
/**
* The site-level token storage port.
*
* @var Token_Storage_Interface
*/
private $token_storage;
/**
* The user-level token storage port.
*
* @var User_Token_Storage_Interface
*/
private $user_token_storage;
/**
* The site URL provider port.
*
* @var Site_URL_Provider_Interface
*/
private $site_url_provider;
/**
* MyYoast_Client constructor.
*
* @param Client_Registration_Interface $client_registration The client registration port.
* @param Authorization_Code_Handler $auth_code_handler The authorization code handler.
* @param OAuth_Grant_Handler $grant_handler The OAuth grant handler.
* @param Token_Revocation_Handler $revocation_handler The token revocation handler.
* @param OAuth_Server_Client_Interface $http_client The OAuth server client port.
* @param Lock_Helper $lock_helper The lock helper.
* @param Token_Storage_Interface $token_storage The site-level token storage port.
* @param User_Token_Storage_Interface $user_token_storage The user-level token storage port.
* @param Site_URL_Provider_Interface $site_url_provider The site URL provider port.
*/
public function __construct(
Client_Registration_Interface $client_registration,
Authorization_Code_Handler $auth_code_handler,
OAuth_Grant_Handler $grant_handler,
Token_Revocation_Handler $revocation_handler,
OAuth_Server_Client_Interface $http_client,
Lock_Helper $lock_helper,
Token_Storage_Interface $token_storage,
User_Token_Storage_Interface $user_token_storage,
Site_URL_Provider_Interface $site_url_provider
) {
$this->client_registration = $client_registration;
$this->auth_code_handler = $auth_code_handler;
$this->grant_handler = $grant_handler;
$this->revocation_handler = $revocation_handler;
$this->http_client = $http_client;
$this->lock_helper = $lock_helper;
$this->token_storage = $token_storage;
$this->user_token_storage = $user_token_storage;
$this->site_url_provider = $site_url_provider;
$this->logger = new NullLogger();
}
/**
* Ensures the plugin is registered as an OAuth client.
*
* @param string[] $redirect_uris The OAuth redirect URIs to register with.
*
* @return Registered_Client The registered client.
*
* @throws Registration_Failed_Exception If registration fails.
*/
public function ensure_registered( array $redirect_uris = [] ): Registered_Client {
return $this->client_registration->ensure_registered( $redirect_uris );
}
/**
* Whether the plugin is registered as an OAuth client.
*
* @param string[] $redirect_uris Optional redirect URIs to verify against the stored registration.
*
* @return bool
*/
public function is_registered( array $redirect_uris = [] ): bool {
return $this->client_registration->is_registered( $redirect_uris );
}
/**
* Reads the current client registration from the server.
*
* @return array<string, string|string[]> The registration metadata.
*
* @throws Registration_Failed_Exception If the read fails.
*/
public function verify_registration(): array {
return $this->client_registration->read_registration();
}
/**
* Deletes the client registration from the server and clears local data.
*
* @return bool True if deleted or not registered, false on network failure.
*/
public function deregister(): bool {
return $this->client_registration->deregister();
}
/**
* Rotates the registration key pair.
*
* @return Registered_Client The updated credentials.
*
* @throws Registration_Failed_Exception If the rotation fails.
*/
public function rotate_registration_keys(): Registered_Client {
return $this->client_registration->rotate_registration_keys();
}
/**
* Rotates the DPoP key pair.
*
* @return void
*/
public function rotate_dpop_keys(): void {
$this->client_registration->rotate_dpop_keys();
}
/**
* Builds the authorization URL for the user authorization flow.
*
* @param int $user_id The WordPress user ID.
* @param string $redirect_uri The callback redirect URI.
* @param string[] $scopes The scopes to request.
* @param string|null $return_url The URL to return the user to after authorization completes.
*
* @return string The authorization URL.
*
* @throws Authorization_Flow_Exception If registration, discovery, or parameter validation fails.
*/
public function get_authorization_url( int $user_id, string $redirect_uri, array $scopes = [], ?string $return_url = null ): string {
return $this->auth_code_handler->get_authorization_url( $user_id, $redirect_uri, $scopes, $return_url );
}
/**
* Exchanges an authorization code for tokens and stores them for the user.
*
* @param int $user_id The WordPress user ID.
* @param string $code The authorization code.
* @param string $state The state parameter from the callback.
*
* @return Token_Set The obtained tokens.
*
* @throws Token_Request_Failed_Exception If the exchange fails.
* @throws Token_Storage_Exception If encrypting the token set for storage fails.
*/
public function exchange_authorization_code( int $user_id, string $code, string $state ): Token_Set {
$token_set = $this->auth_code_handler->exchange_code( $user_id, $code, $state );
$this->user_token_storage->store( $user_id, $token_set );
return $token_set;
}
/**
* Returns a valid site-level access token (client_credentials).
*
* @param string[] $scopes The service:* scopes to request.
*
* @return Token_Set The site-level token set.
*
* @throws Token_Request_Failed_Exception If the token request fails.
* @throws Token_Storage_Exception If encrypting the token set for storage fails.
*/
public function get_site_token( array $scopes = [] ): Token_Set {
$cached = $this->token_storage->get();
if ( $cached !== null && ! $cached->is_expired() && $cached->has_scopes( $scopes ) ) {
return $cached;
}
$grant = new Client_Credentials_Grant( $scopes, $this->site_url_provider->get() );
$token_set = $this->grant_handler->request_token( $grant );
$this->token_storage->store( $token_set );
return $token_set;
}
/**
* Returns a valid user-level access token, auto-refreshing if expired.
*
* @param int $user_id The WordPress user ID.
* @param string[] $required_scopes Optional scopes required for the token; if provided, no token will be returned unless it has at least these scopes.
* This is to avoid refreshing a token that would trigger an immediate re-authorization due to missing scopes.
*
* @return Token_Set|null The user token set, or null if the user hasn't authorized.
*/
public function get_user_token( int $user_id, array $required_scopes = [] ): ?Token_Set {
$token_set = $this->user_token_storage->get( $user_id );
if ( $token_set === null ) {
return null;
}
if ( ! $token_set->has_scopes( $required_scopes ) ) {
// Required scopes are missing, treat as if no token is available.
return null;
}
if ( ! $token_set->is_expired() ) {
return $token_set;
}
$refresh_token = $token_set->get_refresh_token();
if ( $refresh_token === null ) {
return null;
}
try {
// If the client was just re-registered, this refresh will fail with invalid_grant.
// error_count is 0 on first attempt; after one invalid_grant it becomes 1.
// On the second consecutive invalid_grant (error_count >= 1), clear and give up.
$grant = new Refresh_Token_Grant( $refresh_token );
$lock_key = 'wpseo_myyoast_refresh:' . \hash( 'sha256', $refresh_token );
$new_token_set = $this->lock_helper->execute(
$lock_key,
function () use ( $grant ) {
return $this->grant_handler->request_token( $grant );
},
self::REFRESH_LOCK_TTL_IN_SECONDS,
);
try {
$this->user_token_storage->store( $user_id, $new_token_set );
} catch ( Token_Storage_Exception $e ) {
// Next request will re-refresh from the old stored token.
$this->logger->warning( 'Failed to persist refreshed token: {error}', [ 'error' => $e->getMessage() ] );
}
return $new_token_set;
} catch ( Lock_Timeout_Exception $e ) {
// Concurrent refresh in progress, treat as transient failure.
$this->logger->debug( 'Skipping token refresh for user {user_id}: concurrent refresh in progress.', [ 'user_id' => $user_id ] );
return null;
} catch ( Token_Request_Failed_Exception $e ) {
if ( $e->get_error_code() === 'invalid_grant' ) {
if ( $token_set->get_error_count() >= 1 ) {
$this->logger->warning( 'Repeated invalid_grant for user {user_id}, clearing stored tokens.', [ 'user_id' => $user_id ] );
$this->user_token_storage->delete( $user_id );
return null;
}
try {
$this->user_token_storage->store( $user_id, $token_set->with_incremented_error_count() );
} catch ( Token_Storage_Exception $e ) {
// Failure to persist error count is non-critical; token will be retried next request.
$this->logger->warning( 'Failed to persist token error count: {error}', [ 'error' => $e->getMessage() ] );
}
}
return null;
}
}
/**
* Whether the given user has authorized with MyYoast.
*
* @param int $user_id The WordPress user ID.
*
* @return bool
*/
public function has_user_token( int $user_id ): bool {
return $this->user_token_storage->get( $user_id ) !== null;
}
/**
* Revokes the user's tokens and clears storage.
*
* @param int $user_id The WordPress user ID.
*
* @return void
*/
public function revoke_user_token( int $user_id ): void {
$token_set = $this->user_token_storage->get( $user_id );
if ( $token_set === null ) {
return;
}
// Assume tokens are opaque for forwards compatibility. This operation is noop for JWTs, as they are only revoked upon expiration.
$this->revocation_handler->revoke( $token_set->get_access_token(), Token_Type_Hint::ACCESS_TOKEN );
if ( $token_set->get_refresh_token() !== null ) {
$this->revocation_handler->revoke( $token_set->get_refresh_token(), Token_Type_Hint::REFRESH_TOKEN );
}
$this->user_token_storage->delete( $user_id );
}
/**
* Revokes a token at the authorization server.
*
* @param string $token The token to revoke.
* @param string $token_type_hint A Token_Type_Hint constant.
*
* @return bool True if the revocation request was sent.
*/
public function revoke_token(
// phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPNativeAttributeFound -- No-op on PHP < 8.2; redacts parameter from stack traces on PHP 8.2+.
#[SensitiveParameter]
string $token,
string $token_type_hint = Token_Type_Hint::REFRESH_TOKEN
): bool {
return $this->revocation_handler->revoke( $token, $token_type_hint );
}
/**
* Clears the site-level token.
*
* @return void
*/
public function clear_site_token(): void {
$this->token_storage->delete();
}
/**
* Makes an authenticated DPoP-bound request to a resource server.
*
* @param string $method The HTTP method.
* @param string $url The resource URL.
* @param Token_Set $token_set The token set to use.
* @param array<string, string|int|bool> $options Additional request options.
*
* @return HTTP_Response The response.
*/
public function authenticated_request( string $method, string $url, Token_Set $token_set, array $options = [] ): HTTP_Response {
return $this->http_client->authenticated_request(
$method,
$url,
$token_set->get_access_token(),
$token_set->get_token_type(),
$options,
);
}
}