• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 5938ee59b73c38b817588ec25e218ab0a9baa23d

17 Apr 2025 03:07PM UTC coverage: 52.444% (-0.03%) from 52.47%
5938ee59b73c38b817588ec25e218ab0a9baa23d

push

github

mykola
Merge remote-tracking branch 'origin/trunk' into feature/ai-optimize-classic

7837 of 13887 branches covered (56.43%)

Branch coverage included in aggregate %.

28 of 91 new or added lines in 5 files covered. (30.77%)

7 existing lines in 2 files now uncovered.

29071 of 56489 relevant lines covered (51.46%)

42209.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.73
/src/dashboard/infrastructure/integrations/site-kit.php
1
<?php
2
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
3
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Integrations;
4

5
use Google\Site_Kit\Core\REST_API\REST_Routes;
6
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
7
use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface as Configuration_Repository;
8
use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository_Interface;
9
use Yoast\WP\SEO\Dashboard\Infrastructure\Connection\Site_Kit_Is_Connected_Call;
10
use Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor;
11

12
/**
13
 * Describes if the Site kit integration is enabled and configured.
14
 */
15
class Site_Kit {
16

17
        private const SITE_KIT_FILE = 'google-site-kit/google-site-kit.php';
18

19
        /**
20
         * Variable to locally cache the setup completed value.
21
         *
22
         * @var bool $setup_completed
23
         */
24
        private $setup_completed;
25

26
        /**
27
         * The Site Kit consent repository.
28
         *
29
         * @var Site_Kit_Consent_Repository_Interface
30
         */
31
        private $site_kit_consent_repository;
32

33
        /**
34
         * The Site Kit consent repository.
35
         *
36
         * @var Configuration_Repository
37
         */
38
        private $permanently_dismissed_site_kit_configuration_repository;
39

40
        /**
41
         * The call wrapper.
42
         *
43
         * @var Site_Kit_Is_Connected_Call $site_kit_is_connected_call
44
         */
45
        private $site_kit_is_connected_call;
46

47
        /**
48
         * The search console module data.
49
         *
50
         * @var array<string, bool> $search_console_module
51
         */
52
        private $search_console_module = [
53
                'owner'    => null,
54
                'can_view' => false,
55
        ];
56

57
        /**
58
         * The analytics module data.
59
         *
60
         * @var array<string, bool> $ga_module
61
         */
62
        private $ga_module = [
63
                'owner'     => null,
64
                'can_view'  => false,
65
                'connected' => null,
66
        ];
67

68
        /**
69
         * The constructor.
70
         *
71
         * @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository The Site Kit consent repository.
72
         * @param Configuration_Repository              $configuration_repository    The Site Kit permanently dismissed
73
         *                                                                           configuration repository.
74
         * @param Site_Kit_Is_Connected_Call            $site_kit_is_connected_call  The api call to check if the site is
75
         *                                                                           connected.
76
         */
UNCOV
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
        ) {
UNCOV
82
                $this->site_kit_consent_repository                             = $site_kit_consent_repository;
×
UNCOV
83
                $this->permanently_dismissed_site_kit_configuration_repository = $configuration_repository;
×
NEW
84
                $this->site_kit_is_connected_call                              = $site_kit_is_connected_call;
×
85
        }
86

87
        /**
88
         * If the integration is activated.
89
         *
90
         * @return bool If the integration is activated.
91
         */
92
        public function is_enabled(): bool {
18✔
93
                return \is_plugin_active( self::SITE_KIT_FILE );
18✔
94
        }
95

96
        /**
97
         * If the Google site kit setup has been completed.
98
         *
99
         * @return bool If the Google site kit setup has been completed.
100
         */
101
        private function is_setup_completed(): bool {
14✔
102
                if ( $this->setup_completed !== null ) {
14✔
103
                        return $this->setup_completed;
10✔
104
                }
105

106
                return $this->site_kit_is_connected_call->is_setup_completed();
4✔
107
        }
108

109
        /**
110
         * If consent has been granted.
111
         *
112
         * @return bool If consent has been granted.
113
         */
114
        private function is_connected(): bool {
14✔
115
                return $this->site_kit_consent_repository->is_consent_granted();
14✔
116
        }
117

118
        /**
119
         * If Google Analytics is connected.
120
         *
121
         * @return bool If Google Analytics is connected.
122
         */
123
        public function is_ga_connected(): bool {
18✔
124
                if ( $this->ga_module['connected'] !== null ) {
18✔
125
                        return $this->ga_module['connected'];
10✔
126
                }
127

128
                return $this->site_kit_is_connected_call->is_ga_connected();
8✔
129
        }
130

131
        /**
132
         * If the Site Kit plugin is installed. This is needed since we cannot check with `is_plugin_active` in rest
133
         * requests. `Plugin.php` is only loaded on admin pages.
134
         *
135
         * @return bool If the Site Kit plugin is installed.
136
         */
NEW
137
        private function is_site_kit_installed(): bool {
×
UNCOV
138
                return \class_exists( 'Google\Site_Kit\Plugin' );
×
139
        }
140

141
        /**
142
         * If the entire onboarding has been completed.
143
         *
144
         * @return bool If the entire onboarding has been completed.
145
         */
NEW
146
        public function is_onboarded(): bool {
×
UNCOV
147
                return ( $this->is_site_kit_installed() && $this->is_setup_completed() && $this->is_connected() );
×
148
        }
149

150
        /**
151
         * Checks if current user is owner of the module.
152
         *
153
         * @param array<string>|null $module_owner The module to check for owner.
154
         *
155
         * @return bool If current user is owner of the module.
156
         */
NEW
157
        public function is_owner( ?array $module_owner ): bool {
×
UNCOV
158
                $current_user = \wp_get_current_user();
×
NEW
159
                if ( $module_owner !== null ) {
×
NEW
160
                        return (int) $module_owner['id'] === $current_user->ID;
×
161

162
                }
163

NEW
164
                return false;
×
165
        }
166

167
        /**
168
         * Checks is current user can view dashboard data, which can the owner who set it up,
169
         * or user with one of the shared roles.
170
         *
171
         * @param array<array|null> $module The module owner.
172
         *
173
         * @return bool If the user can read the data.
174
         */
175
        private function can_read_data( array $module ): bool {
14✔
176
                return $module['can_view'] || $this->is_owner( $module['owner'] );
14✔
177
        }
178

179
        /**
180
         * Return this object represented by a key value array.
181
         *
182
         * @return array<string, bool> Returns the name and if the feature is enabled.
183
         */
184
        public function to_array(): array {
16✔
185
                if ( ! ( new Google_Site_Kit_Feature_Conditional() )->is_met() ) {
16✔
186
                        return [];
2✔
187
                }
188
                if ( $this->is_enabled() ) {
14✔
189
                        $this->parse_site_kit_data();
10✔
190
                }
191
                return [
14✔
192
                        'installUrl'               => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_install_url() ),
14✔
193
                        'activateUrl'              => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_activate_url() ),
14✔
194
                        'setupUrl'                 => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_setup_url() ),
14✔
195
                        'updateUrl'                => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_update_url() ),
14✔
196
                        'dashboardUrl'             => \self_admin_url( 'admin.php?page=googlesitekit-dashboard' ),
14✔
197
                        'isAnalyticsConnected'     => $this->is_ga_connected(),
14✔
198
                        'isFeatureEnabled'         => true,
14✔
199
                        'isSetupWidgetDismissed'   => $this->permanently_dismissed_site_kit_configuration_repository->is_site_kit_configuration_dismissed(),
14✔
200
                        'capabilities'             => [
14✔
201
                                'installPlugins'        => \current_user_can( 'install_plugins' ),
14✔
202
                                'viewSearchConsoleData' => $this->can_read_data( $this->search_console_module ),
14✔
203
                                'viewAnalyticsData'     => $this->can_read_data( $this->ga_module ),
14✔
204
                        ],
14✔
205
                        'connectionStepsStatuses'  => [
14✔
206
                                'isInstalled'      => \file_exists( \WP_PLUGIN_DIR . '/' . self::SITE_KIT_FILE ),
14✔
207
                                'isActive'         => $this->is_enabled(),
14✔
208
                                'isSetupCompleted' => $this->is_setup_completed(),
14✔
209
                                'isConsentGranted' => $this->is_connected(),
14✔
210
                        ],
14✔
211
                        'isVersionSupported'       => \defined( 'GOOGLESITEKIT_VERSION' ) ? \version_compare( \GOOGLESITEKIT_VERSION, '1.148.0', '>=' ) : false,
14✔
212
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
213
                        'isRedirectedFromSiteKit'  => isset( $_GET['redirected_from_site_kit'] ),
14✔
214
                ];
14✔
215
        }
216

217
        /**
218
         * Return this object represented by a key value array. This is not used yet.
219
         *
220
         * @codeCoverageIgnore
221
         *
222
         * @return array<string, bool> Returns the name and if the feature is enabled.
223
         */
224
        public function to_legacy_array(): array {
225
                return $this->to_array();
226
        }
227

228
        /**
229
         * Parses the Site Kit configuration data.
230
         *
231
         * @return void
232
         */
NEW
233
        public function parse_site_kit_data(): void {
×
NEW
234
                $paths     = $this->get_preload_paths();
×
NEW
235
                $preloaded = $this->get_preloaded_data( $paths );
×
NEW
236
                if ( empty( $preloaded ) ) {
×
NEW
237
                        return;
×
238
                }
239

NEW
240
                $modules_data        = ! empty( $preloaded[ $paths['modules'] ]['body'] ) ? $preloaded[ $paths['modules'] ]['body'] : [];
×
NEW
241
                $modules_permissions = ! empty( $preloaded[ $paths['permissions'] ]['body'] ) ? $preloaded[ $paths['permissions'] ]['body'] : [];
×
NEW
242
                $is_authenticated    = false;
×
NEW
243
                if ( ! empty( $preloaded[ $paths['authentication'] ]['body']['authenticated'] ) ) {
×
NEW
244
                        $is_authenticated = $preloaded[ $paths['authentication'] ]['body']['authenticated'];
×
245
                }
NEW
246
                $this->setup_completed = $preloaded[ $paths['connection'] ]['body']['setupCompleted'];
×
247

NEW
248
                foreach ( $modules_data as $module ) {
×
NEW
249
                        $slug = $module['slug'];
×
NEW
250
                        if ( $slug === 'analytics-4' ) {
×
NEW
251
                                $this->ga_module['owner']     = ( $module['owner'] ?? null );
×
NEW
252
                                $this->ga_module['connected'] = ( $module['connected'] ?? false );
×
NEW
253
                                if ( isset( $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'] ) ) {
×
NEW
254
                                        $this->ga_module['can_view'] = $is_authenticated || $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'];
×
255
                                }
256
                        }
NEW
257
                        if ( $slug === 'search-console' ) {
×
NEW
258
                                $this->search_console_module['owner'] = ( $module['owner'] ?? null );
×
259

NEW
260
                                if ( isset( $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'] ) ) {
×
NEW
261
                                        $this->search_console_module['can_view'] = $is_authenticated || $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'];
×
262
                                }
263
                        }
264
                }
265
        }
266

267
        /**
268
         * Holds the parsed preload paths for preloading some Site Kit API data.
269
         *
270
         * @return string[]
271
         */
NEW
272
        public function get_preload_paths(): array {
×
273

NEW
274
                $rest_root = ( \class_exists( REST_Routes::class ) ) ? REST_Routes::REST_ROOT : '';
×
275

NEW
276
                return [
×
NEW
277
                        'authentication' => '/' . $rest_root . '/core/user/data/authentication',
×
NEW
278
                        'permissions'    => '/' . $rest_root . '/core/user/data/permissions',
×
NEW
279
                        'modules'        => '/' . $rest_root . '/core/modules/data/list',
×
NEW
280
                        'connection'     => '/' . $rest_root . '/core/site/data/connection',
×
NEW
281
                ];
×
282
        }
283

284
        /**
285
         * Runs the given paths through the `rest_preload_api_request` method.
286
         *
287
         * @param string[] $paths The paths to add to `rest_preload_api_request`.
288
         *
289
         * @return array<array|null> The array with all the now filled in preloaded data.
290
         */
NEW
291
        public function get_preloaded_data( array $paths ): array {
×
NEW
292
                $preload_paths = \apply_filters( 'googlesitekit_apifetch_preload_paths', [] );
×
NEW
293
                $actual_paths  = \array_intersect( $paths, $preload_paths );
×
294

295
                return \array_reduce(
296
                        \array_unique( $actual_paths ),
297
                        'rest_preload_api_request',
298
                        []
299
                );
300
        }
301

302
        /**
303
         * Creates a valid activation URL for the Site Kit plugin.
304
         *
305
         * @return string
306
         */
307
        public function get_activate_url(): string {
308
                return \html_entity_decode(
309
                        \wp_nonce_url(
310
                                \self_admin_url( 'plugins.php?action=activate&plugin=' . self::SITE_KIT_FILE ),
311
                                'activate-plugin_' . self::SITE_KIT_FILE
312
                        )
313
                );
314
        }
315

316
        /**
317
         *  Creates a valid install URL for the Site Kit plugin.
318
         *
319
         * @return string
320
         */
321
        public function get_install_url(): string {
322
                return \html_entity_decode(
323
                        \wp_nonce_url(
324
                                \self_admin_url( 'update.php?action=install-plugin&plugin=google-site-kit' ),
325
                                'install-plugin_google-site-kit'
326
                        )
327
                );
328
        }
329

330
        /**
331
         *  Creates a valid update URL for the Site Kit plugin.
332
         *
333
         * @return string
334
         */
335
        public function get_update_url(): string {
336
                return \html_entity_decode(
337
                        \wp_nonce_url(
338
                                \self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . self::SITE_KIT_FILE ),
339
                                'upgrade-plugin_' . self::SITE_KIT_FILE
340
                        )
341
                );
342
        }
343

344
        /**
345
         *  Creates a valid setup URL for the Site Kit plugin.
346
         *
347
         * @return string
348
         */
349
        public function get_setup_url(): string {
350
                return \self_admin_url( 'admin.php?page=googlesitekit-splash' );
351
        }
352
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc