PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
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 / dashboard / infrastructure / integrations / site-kit.php

site-kit.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at src/dashboard/infrastructure/integrations/site-kit.php

327 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4 namespace Yoast\WP\SEO\Dashboard\Infrastructure\Integrations;
5
6 use Google\Site_Kit\Core\REST_API\REST_Routes;
7 use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional;
8 use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface as Configuration_Repository;
9 use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository_Interface;
10 use Yoast\WP\SEO\Dashboard\Infrastructure\Connection\Site_Kit_Is_Connected_Call;
11 use Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor;
12
13 /**
14 * Describes if the Site kit integration is enabled and configured.
15 */
16 class Site_Kit {
17
18 private const SITE_KIT_FILE = 'google-site-kit/google-site-kit.php';
19
20 /**
21 * The Site Kit conditional.
22 *
23 * @var Site_Kit_Conditional
24 */
25 private $site_kit_conditional;
26
27 /**
28 * The Site Kit consent repository.
29 *
30 * @var Site_Kit_Consent_Repository_Interface
31 */
32 private $site_kit_consent_repository;
33
34 /**
35 * The Site Kit consent repository.
36 *
37 * @var Configuration_Repository
38 */
39 private $permanently_dismissed_site_kit_configuration_repository;
40
41 /**
42 * The call wrapper.
43 *
44 * @var Site_Kit_Is_Connected_Call $site_kit_is_connected_call
45 */
46 private $site_kit_is_connected_call;
47
48 /**
49 * The search console module data.
50 *
51 * @var array<string, bool> $search_console_module
52 */
53 private $search_console_module = [
54 'can_view' => null,
55 ];
56
57 /**
58 * The analytics module data.
59 *
60 * @var array<string, bool> $ga_module
61 */
62 private $ga_module = [
63 'can_view' => null,
64 'connected' => null,
65 ];
66
67 /**
68 * The constructor.
69 *
70 * @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository The Site Kit consent repository.
71 * @param Configuration_Repository $configuration_repository The Site Kit permanently dismissed
72 * configuration repository.
73 * @param Site_Kit_Is_Connected_Call $site_kit_is_connected_call The api call to check if the site is
74 * connected.
75 * @param Site_Kit_Conditional $site_kit_conditional The Site Kit conditional.
76 */
77 public function __construct(
78 Site_Kit_Consent_Repository_Interface $site_kit_consent_repository,
79 Configuration_Repository $configuration_repository,
80 Site_Kit_Is_Connected_Call $site_kit_is_connected_call,
81 Site_Kit_Conditional $site_kit_conditional
82 ) {
83 $this->site_kit_consent_repository = $site_kit_consent_repository;
84 $this->permanently_dismissed_site_kit_configuration_repository = $configuration_repository;
85 $this->site_kit_is_connected_call = $site_kit_is_connected_call;
86 $this->site_kit_conditional = $site_kit_conditional;
87 }
88
89 /**
90 * If the Site Kit plugin is active.
91 *
92 * @return bool If the integration is activated.
93 */
94 public function is_enabled(): bool {
95 return $this->site_kit_conditional->is_met();
96 }
97
98 /**
99 * If the Google site kit setup has been completed.
100 *
101 * @return bool If the Google site kit setup has been completed.
102 */
103 private function is_setup_completed(): bool {
104 return $this->site_kit_is_connected_call->is_setup_completed();
105 }
106
107 /**
108 * If consent has been granted.
109 *
110 * @return bool If consent has been granted.
111 */
112 private function is_connected(): bool {
113 return $this->site_kit_consent_repository->is_consent_granted();
114 }
115
116 /**
117 * If Google Analytics is connected.
118 *
119 * @return bool If Google Analytics is connected.
120 */
121 public function is_ga_connected(): bool {
122 if ( $this->ga_module['connected'] !== null ) {
123 return $this->ga_module['connected'];
124 }
125
126 return $this->site_kit_is_connected_call->is_ga_connected();
127 }
128
129 /**
130 * If the Site Kit plugin is installed. This is needed since we cannot check with `is_plugin_active` in rest
131 * requests. `Plugin.php` is only loaded on admin pages.
132 *
133 * @return bool If the Site Kit plugin is installed.
134 */
135 private function is_site_kit_installed(): bool {
136 return \class_exists( 'Google\Site_Kit\Plugin' );
137 }
138
139 /**
140 * If the entire onboarding has been completed.
141 *
142 * @return bool If the entire onboarding has been completed.
143 */
144 public function is_onboarded(): bool {
145 // @TODO: Consider replacing the `is_setup_completed()` check with a `can_read_data( $module )` check (and possibly rename the method to something more genric eg. is_ready() ).
146 return ( $this->is_site_kit_installed() && $this->is_setup_completed() && $this->is_connected() );
147 }
148
149 /**
150 * Checks if current user can view dashboard data for a module
151 *
152 * @param array<array|null> $module The module.
153 *
154 * @return bool If the user can read the data.
155 */
156 private function can_read_data( array $module ): bool {
157 return ( $module['can_view'] ?? false );
158 }
159
160 /**
161 * Return this object represented by a key value array.
162 *
163 * @return array<string, bool> Returns the name and if the feature is enabled.
164 */
165 public function to_array(): array {
166 if ( $this->is_enabled() ) {
167 $this->parse_site_kit_data();
168 }
169 return [
170 'installUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_install_url() ),
171 'activateUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_activate_url() ),
172 'setupUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_setup_url() ),
173 'updateUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_update_url() ),
174 'dashboardUrl' => \self_admin_url( 'admin.php?page=googlesitekit-dashboard' ),
175 'isAnalyticsConnected' => $this->is_ga_connected(),
176 'isFeatureEnabled' => true,
177 'isSetupWidgetDismissed' => $this->permanently_dismissed_site_kit_configuration_repository->is_site_kit_configuration_dismissed(),
178 'capabilities' => [
179 'installPlugins' => \current_user_can( 'install_plugins' ),
180 'viewSearchConsoleData' => $this->can_read_data( $this->search_console_module ),
181 'viewAnalyticsData' => $this->can_read_data( $this->ga_module ),
182 ],
183 'connectionStepsStatuses' => [
184 'isInstalled' => \file_exists( \WP_PLUGIN_DIR . '/' . self::SITE_KIT_FILE ),
185 'isActive' => $this->is_enabled(),
186 'isSetupCompleted' => $this->can_read_data( $this->search_console_module ) || $this->can_read_data( $this->ga_module ),
187 'isConsentGranted' => $this->is_connected(),
188 ],
189 'isVersionSupported' => \defined( 'GOOGLESITEKIT_VERSION' ) ? \version_compare( \GOOGLESITEKIT_VERSION, '1.148.0', '>=' ) : false,
190 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
191 'isRedirectedFromSiteKit' => isset( $_GET['redirected_from_site_kit'] ),
192 ];
193 }
194
195 /**
196 * Return this object represented by a key value array. This is not used yet.
197 *
198 * @codeCoverageIgnore
199 *
200 * @return array<string, bool> Returns the name and if the feature is enabled.
201 */
202 public function to_legacy_array(): array {
203 return $this->to_array();
204 }
205
206 /**
207 * Parses the Site Kit configuration data.
208 *
209 * @return void
210 */
211 public function parse_site_kit_data(): void {
212 $paths = $this->get_preload_paths();
213 $preloaded = $this->get_preloaded_data( $paths );
214 if ( empty( $preloaded ) ) {
215 return;
216 }
217
218 $modules_data = ! empty( $preloaded[ $paths['modules'] ]['body'] ) ? $preloaded[ $paths['modules'] ]['body'] : [];
219 $modules_permissions = ! empty( $preloaded[ $paths['permissions'] ]['body'] ) ? $preloaded[ $paths['permissions'] ]['body'] : [];
220
221 $can_view_dashboard = ( $modules_permissions['googlesitekit_view_authenticated_dashboard'] ?? false );
222
223 foreach ( $modules_data as $module ) {
224 $slug = $module['slug'];
225 // We have to also check if the module is recoverable, because if we rely on the module being shared, we have to make also sure the module owner is still connected.
226 $is_recoverable = ( $module['recoverable'] ?? null );
227
228 if ( $slug === 'analytics-4' ) {
229 $can_read_shared_module_data = ( $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'] ?? false );
230
231 $this->ga_module['can_view'] = $can_view_dashboard || ( $can_read_shared_module_data && ! $is_recoverable );
232 $this->ga_module['connected'] = ( $module['connected'] ?? false );
233 }
234
235 if ( $slug === 'search-console' ) {
236 $can_read_shared_module_data = ( $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'] ?? false );
237
238 $this->search_console_module['can_view'] = $can_view_dashboard || ( $can_read_shared_module_data && ! $is_recoverable );
239 }
240 }
241 }
242
243 /**
244 * Holds the parsed preload paths for preloading some Site Kit API data.
245 *
246 * @return string[]
247 */
248 public function get_preload_paths(): array {
249
250 $rest_root = ( \class_exists( REST_Routes::class ) ) ? REST_Routes::REST_ROOT : '';
251
252 return [
253 'permissions' => '/' . $rest_root . '/core/user/data/permissions',
254 'modules' => '/' . $rest_root . '/core/modules/data/list',
255 ];
256 }
257
258 /**
259 * Runs the given paths through the `rest_preload_api_request` method.
260 *
261 * @param string[] $paths The paths to add to `rest_preload_api_request`.
262 *
263 * @return array<array|null> The array with all the now filled in preloaded data.
264 */
265 public function get_preloaded_data( array $paths ): array {
266 $preload_paths = \apply_filters( 'googlesitekit_apifetch_preload_paths', [] );
267 $actual_paths = \array_intersect( $paths, $preload_paths );
268
269 return \array_reduce(
270 \array_unique( $actual_paths ),
271 'rest_preload_api_request',
272 [],
273 );
274 }
275
276 /**
277 * Creates a valid activation URL for the Site Kit plugin.
278 *
279 * @return string
280 */
281 public function get_activate_url(): string {
282 return \html_entity_decode(
283 \wp_nonce_url(
284 \self_admin_url( 'plugins.php?action=activate&plugin=' . self::SITE_KIT_FILE ),
285 'activate-plugin_' . self::SITE_KIT_FILE,
286 ),
287 );
288 }
289
290 /**
291 * Creates a valid install URL for the Site Kit plugin.
292 *
293 * @return string
294 */
295 public function get_install_url(): string {
296 return \html_entity_decode(
297 \wp_nonce_url(
298 \self_admin_url( 'update.php?action=install-plugin&plugin=google-site-kit' ),
299 'install-plugin_google-site-kit',
300 ),
301 );
302 }
303
304 /**
305 * Creates a valid update URL for the Site Kit plugin.
306 *
307 * @return string
308 */
309 public function get_update_url(): string {
310 return \html_entity_decode(
311 \wp_nonce_url(
312 \self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . self::SITE_KIT_FILE ),
313 'upgrade-plugin_' . self::SITE_KIT_FILE,
314 ),
315 );
316 }
317
318 /**
319 * Creates a valid setup URL for the Site Kit plugin.
320 *
321 * @return string
322 */
323 public function get_setup_url(): string {
324 return \self_admin_url( 'admin.php?page=googlesitekit-splash' );
325 }
326 }
327