PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / myyoast-client / user-interface / status-presenter.php

status-presenter.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI trunk, at src/myyoast-client/user-interface/status-presenter.php

183 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
3 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4
5 namespace Yoast\WP\SEO\MyYoast_Client\User_Interface;
6
7 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Client_Registration_Interface;
8 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Redirect_URI_Provider_Interface;
9 use Yoast\WP\SEO\MyYoast_Client\Domain\Registered_Client;
10 use Yoast\WP\SEO\MyYoast_Client\Infrastructure\OIDC\Issuer_Config;
11
12 /**
13 * Builds the status payload surfaced on the MyYoast connection card on the integrations page.
14 *
15 * Intentionally narrow: registration state, registration date, and the
16 * stored redirect URIs with their verification state. Software statement,
17 * IAT, RAT, client ID, scopes, and token data are never included.
18 */
19 class Status_Presenter {
20
21 /**
22 * The client registration port.
23 *
24 * @var Client_Registration_Interface
25 */
26 private $client_registration;
27
28 /**
29 * The issuer configuration.
30 *
31 * @var Issuer_Config
32 */
33 private $issuer_config;
34
35 /**
36 * The redirect URI provider.
37 *
38 * @var Redirect_URI_Provider_Interface
39 */
40 private $redirect_uri_provider;
41
42 /**
43 * Status_Presenter constructor.
44 *
45 * @param Client_Registration_Interface $client_registration The client registration port.
46 * @param Issuer_Config $issuer_config The issuer configuration.
47 * @param Redirect_URI_Provider_Interface $redirect_uri_provider The redirect URI provider.
48 */
49 public function __construct(
50 Client_Registration_Interface $client_registration,
51 Issuer_Config $issuer_config,
52 Redirect_URI_Provider_Interface $redirect_uri_provider
53 ) {
54 $this->client_registration = $client_registration;
55 $this->issuer_config = $issuer_config;
56 $this->redirect_uri_provider = $redirect_uri_provider;
57 }
58
59 /**
60 * Returns the current status payload.
61 *
62 * @return array{is_provisioned: bool, is_registered: bool, registered_at: int|null, registered_at_iso: string|null, redirect_uris: array<int, array{uri: string, origin: string, is_verified: bool}>, redirect_uris_match: bool}
63 */
64 public function present(): array {
65 $is_provisioned = ( $this->issuer_config->get_software_statement() !== '' )
66 && ( $this->issuer_config->get_initial_access_token() !== '' );
67
68 $registered_client = $this->client_registration->get_registered_client();
69 $is_registered = ( $registered_client !== null );
70
71 $registered_at = null;
72 $registered_at_iso = null;
73 $redirect_uris = [];
74 $redirect_uris_match = true;
75
76 if ( $registered_client !== null ) {
77 $registered_at = $this->extract_registered_at( $registered_client );
78 $registered_at_iso = ( $registered_at !== null ) ? \gmdate( 'c', $registered_at ) : null;
79 $redirect_uris = $this->extract_redirect_uris( $registered_client );
80 $redirect_uris_match = $this->redirect_uris_match( $registered_client );
81 }
82
83 return [
84 'is_provisioned' => $is_provisioned,
85 'is_registered' => $is_registered,
86 'registered_at' => $registered_at,
87 'registered_at_iso' => $registered_at_iso,
88 'redirect_uris' => $redirect_uris,
89 'redirect_uris_match' => $redirect_uris_match,
90 ];
91 }
92
93 /**
94 * Extracts the registration timestamp (RFC 7591 `client_id_issued_at`).
95 *
96 * @param Registered_Client $client The registered client.
97 *
98 * @return int|null Unix timestamp, or null if absent or not coercible.
99 */
100 private function extract_registered_at( Registered_Client $client ): ?int {
101 $metadata = $client->get_metadata();
102 if ( ! isset( $metadata['client_id_issued_at'] ) ) {
103 return null;
104 }
105
106 $value = $metadata['client_id_issued_at'];
107 if ( ! \is_numeric( $value ) ) {
108 return null;
109 }
110
111 $timestamp = (int) $value;
112 return ( $timestamp > 0 ) ? $timestamp : null;
113 }
114
115 /**
116 * Whether the registration's redirect URIs still match what this site would register today.
117 *
118 * A mismatch means the site's URL has changed since it was connected, and the
119 * registration needs re-syncing.
120 *
121 * @param Registered_Client $client The registered client.
122 *
123 * @return bool
124 */
125 private function redirect_uris_match( Registered_Client $client ): bool {
126 return $client->has_redirect_uris( $this->redirect_uri_provider->get_redirect_uris() );
127 }
128
129 /**
130 * Returns the stored redirect URIs annotated with their origin (scheme +
131 * host + optional port) and their verification state.
132 *
133 * A URI is verified once a user has completed the authorization-code flow for
134 * it on this site; that state is tracked on the registration.
135 *
136 * @param Registered_Client $client The registered client.
137 *
138 * @return array<int, array{uri: string, origin: string, is_verified: bool}>
139 */
140 private function extract_redirect_uris( Registered_Client $client ): array {
141 $result = [];
142 foreach ( $client->get_redirect_uris() as $uri ) {
143 if ( ! \is_string( $uri ) || $uri === '' ) {
144 continue;
145 }
146
147 $origin = $this->extract_origin( $uri );
148 if ( $origin === null ) {
149 continue;
150 }
151
152 $result[] = [
153 'uri' => $uri,
154 'origin' => $origin,
155 'is_verified' => $client->is_uri_validated( $uri ),
156 ];
157 }
158
159 return $result;
160 }
161
162 /**
163 * Extracts the origin (scheme + host + optional port) from a URI.
164 *
165 * @param string $uri The URI to parse.
166 *
167 * @return string|null The origin, or null if the URI couldn't be parsed.
168 */
169 private function extract_origin( string $uri ): ?string {
170 $parts = \wp_parse_url( $uri );
171 if ( ! \is_array( $parts ) || empty( $parts['scheme'] ) || empty( $parts['host'] ) ) {
172 return null;
173 }
174
175 $origin = $parts['scheme'] . '://' . $parts['host'];
176 if ( isset( $parts['port'] ) ) {
177 $origin .= ':' . $parts['port'];
178 }
179
180 return $origin;
181 }
182 }
183