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

Yoast / wordpress-seo / f8d50e5bfa3024442897862d50cfa2d5d6a2081e

30 Apr 2025 02:12PM UTC coverage: 52.524% (+0.08%) from 52.444%
f8d50e5bfa3024442897862d50cfa2d5d6a2081e

push

github

FAMarfuaty
Merge branch 'trunk' of github.com:Yoast/wordpress-seo into feature/ai-optimize-classic

7892 of 13944 branches covered (56.6%)

Branch coverage included in aggregate %.

98 of 118 new or added lines in 22 files covered. (83.05%)

2 existing lines in 2 files now uncovered.

29150 of 56580 relevant lines covered (51.52%)

42191.89 hits per line

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

47.19
/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
         * The Site Kit feature conditional.
21
         *
22
         * @var Google_Site_Kit_Feature_Conditional
23
         */
24
        protected $site_kit_feature_conditional;
25

26
        /**
27
         * Variable to locally cache the setup completed value.
28
         *
29
         * @var bool $setup_completed
30
         */
31
        private $setup_completed;
32

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

40
        /**
41
         * The Site Kit consent repository.
42
         *
43
         * @var Configuration_Repository
44
         */
45
        private $permanently_dismissed_site_kit_configuration_repository;
46

47
        /**
48
         * The call wrapper.
49
         *
50
         * @var Site_Kit_Is_Connected_Call $site_kit_is_connected_call
51
         */
52
        private $site_kit_is_connected_call;
53

54
        /**
55
         * The search console module data.
56
         *
57
         * @var array<string, bool> $search_console_module
58
         */
59
        private $search_console_module = [
60
                'owner'    => null,
61
                'can_view' => false,
62
        ];
63

64
        /**
65
         * The analytics module data.
66
         *
67
         * @var array<string, bool> $ga_module
68
         */
69
        private $ga_module = [
70
                'owner'     => null,
71
                'can_view'  => false,
72
                'connected' => null,
73
        ];
74

75
        /**
76
         * The constructor.
77
         *
78
         * @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository  The Site Kit consent repository.
79
         * @param Configuration_Repository              $configuration_repository     The Site Kit permanently dismissed
80
         *                                                                            configuration repository.
81
         * @param Site_Kit_Is_Connected_Call            $site_kit_is_connected_call   The api call to check if the site is
82
         *                                                                            connected.
83
         * @param Google_Site_Kit_Feature_Conditional   $site_kit_feature_conditional The Site Kit feature conditional.
84
         */
85
        public function __construct(
×
86
                Site_Kit_Consent_Repository_Interface $site_kit_consent_repository,
87
                Configuration_Repository $configuration_repository,
88
                Site_Kit_Is_Connected_Call $site_kit_is_connected_call,
89
                Google_Site_Kit_Feature_Conditional $site_kit_feature_conditional
90
        ) {
91
                $this->site_kit_consent_repository                             = $site_kit_consent_repository;
×
92
                $this->permanently_dismissed_site_kit_configuration_repository = $configuration_repository;
×
93
                $this->site_kit_is_connected_call                              = $site_kit_is_connected_call;
×
NEW
94
                $this->site_kit_feature_conditional                            = $site_kit_feature_conditional;
×
95
        }
96

97
        /**
98
         * If the integration is activated.
99
         *
100
         * @return bool If the integration is activated.
101
         */
102
        public function is_enabled(): bool {
18✔
103
                return \is_plugin_active( self::SITE_KIT_FILE );
18✔
104
        }
105

106
        /**
107
         * If the Google site kit setup has been completed.
108
         *
109
         * @return bool If the Google site kit setup has been completed.
110
         */
111
        private function is_setup_completed(): bool {
14✔
112
                if ( $this->setup_completed !== null ) {
14✔
113
                        return $this->setup_completed;
10✔
114
                }
115

116
                return $this->site_kit_is_connected_call->is_setup_completed();
4✔
117
        }
118

119
        /**
120
         * If consent has been granted.
121
         *
122
         * @return bool If consent has been granted.
123
         */
124
        private function is_connected(): bool {
14✔
125
                return $this->site_kit_consent_repository->is_consent_granted();
14✔
126
        }
127

128
        /**
129
         * If Google Analytics is connected.
130
         *
131
         * @return bool If Google Analytics is connected.
132
         */
133
        public function is_ga_connected(): bool {
18✔
134
                if ( $this->ga_module['connected'] !== null ) {
18✔
135
                        return $this->ga_module['connected'];
10✔
136
                }
137

138
                return $this->site_kit_is_connected_call->is_ga_connected();
8✔
139
        }
140

141
        /**
142
         * If the Site Kit plugin is installed. This is needed since we cannot check with `is_plugin_active` in rest
143
         * requests. `Plugin.php` is only loaded on admin pages.
144
         *
145
         * @return bool If the Site Kit plugin is installed.
146
         */
147
        private function is_site_kit_installed(): bool {
×
148
                return \class_exists( 'Google\Site_Kit\Plugin' );
×
149
        }
150

151
        /**
152
         * If the entire onboarding has been completed.
153
         *
154
         * @return bool If the entire onboarding has been completed.
155
         */
156
        public function is_onboarded(): bool {
×
157
                return ( $this->is_site_kit_installed() && $this->is_setup_completed() && $this->is_connected() );
×
158
        }
159

160
        /**
161
         * Checks if current user is owner of the module.
162
         *
163
         * @param array<string>|null $module_owner The module to check for owner.
164
         *
165
         * @return bool If current user is owner of the module.
166
         */
167
        public function is_owner( ?array $module_owner ): bool {
×
168
                $current_user = \wp_get_current_user();
×
169
                if ( $module_owner !== null ) {
×
170
                        return (int) $module_owner['id'] === $current_user->ID;
×
171

172
                }
173

174
                return false;
×
175
        }
176

177
        /**
178
         * Checks is current user can view dashboard data, which can the owner who set it up,
179
         * or user with one of the shared roles.
180
         *
181
         * @param array<array|null> $module The module owner.
182
         *
183
         * @return bool If the user can read the data.
184
         */
185
        private function can_read_data( array $module ): bool {
14✔
186
                return $module['can_view'] || $this->is_owner( $module['owner'] );
14✔
187
        }
188

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

227
        /**
228
         * Return this object represented by a key value array. This is not used yet.
229
         *
230
         * @codeCoverageIgnore
231
         *
232
         * @return array<string, bool> Returns the name and if the feature is enabled.
233
         */
234
        public function to_legacy_array(): array {
235
                return $this->to_array();
236
        }
237

238
        /**
239
         * Parses the Site Kit configuration data.
240
         *
241
         * @return void
242
         */
243
        public function parse_site_kit_data(): void {
×
244
                $paths     = $this->get_preload_paths();
×
245
                $preloaded = $this->get_preloaded_data( $paths );
×
246
                if ( empty( $preloaded ) ) {
×
247
                        return;
×
248
                }
249

250
                $modules_data        = ! empty( $preloaded[ $paths['modules'] ]['body'] ) ? $preloaded[ $paths['modules'] ]['body'] : [];
×
251
                $modules_permissions = ! empty( $preloaded[ $paths['permissions'] ]['body'] ) ? $preloaded[ $paths['permissions'] ]['body'] : [];
×
252
                $is_authenticated    = false;
×
253
                if ( ! empty( $preloaded[ $paths['authentication'] ]['body']['authenticated'] ) ) {
×
254
                        $is_authenticated = $preloaded[ $paths['authentication'] ]['body']['authenticated'];
×
255
                }
256
                $this->setup_completed = $preloaded[ $paths['connection'] ]['body']['setupCompleted'];
×
257

258
                foreach ( $modules_data as $module ) {
×
259
                        $slug = $module['slug'];
×
260
                        if ( $slug === 'analytics-4' ) {
×
261
                                $this->ga_module['owner']     = ( $module['owner'] ?? null );
×
262
                                $this->ga_module['connected'] = ( $module['connected'] ?? false );
×
263
                                if ( isset( $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'] ) ) {
×
264
                                        $this->ga_module['can_view'] = $is_authenticated || $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'];
×
265
                                }
266
                        }
267
                        if ( $slug === 'search-console' ) {
×
268
                                $this->search_console_module['owner'] = ( $module['owner'] ?? null );
×
269

270
                                if ( isset( $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'] ) ) {
×
271
                                        $this->search_console_module['can_view'] = $is_authenticated || $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'];
×
272
                                }
273
                        }
274
                }
275
        }
276

277
        /**
278
         * Holds the parsed preload paths for preloading some Site Kit API data.
279
         *
280
         * @return string[]
281
         */
282
        public function get_preload_paths(): array {
×
283

284
                $rest_root = ( \class_exists( REST_Routes::class ) ) ? REST_Routes::REST_ROOT : '';
×
285

286
                return [
×
287
                        'authentication' => '/' . $rest_root . '/core/user/data/authentication',
×
288
                        'permissions'    => '/' . $rest_root . '/core/user/data/permissions',
×
289
                        'modules'        => '/' . $rest_root . '/core/modules/data/list',
×
290
                        'connection'     => '/' . $rest_root . '/core/site/data/connection',
×
291
                ];
×
292
        }
293

294
        /**
295
         * Runs the given paths through the `rest_preload_api_request` method.
296
         *
297
         * @param string[] $paths The paths to add to `rest_preload_api_request`.
298
         *
299
         * @return array<array|null> The array with all the now filled in preloaded data.
300
         */
301
        public function get_preloaded_data( array $paths ): array {
×
302
                $preload_paths = \apply_filters( 'googlesitekit_apifetch_preload_paths', [] );
×
303
                $actual_paths  = \array_intersect( $paths, $preload_paths );
×
304

305
                return \array_reduce(
306
                        \array_unique( $actual_paths ),
307
                        'rest_preload_api_request',
308
                        []
309
                );
310
        }
311

312
        /**
313
         * Creates a valid activation URL for the Site Kit plugin.
314
         *
315
         * @return string
316
         */
317
        public function get_activate_url(): string {
318
                return \html_entity_decode(
319
                        \wp_nonce_url(
320
                                \self_admin_url( 'plugins.php?action=activate&plugin=' . self::SITE_KIT_FILE ),
321
                                'activate-plugin_' . self::SITE_KIT_FILE
322
                        )
323
                );
324
        }
325

326
        /**
327
         *  Creates a valid install URL for the Site Kit plugin.
328
         *
329
         * @return string
330
         */
331
        public function get_install_url(): string {
332
                return \html_entity_decode(
333
                        \wp_nonce_url(
334
                                \self_admin_url( 'update.php?action=install-plugin&plugin=google-site-kit' ),
335
                                'install-plugin_google-site-kit'
336
                        )
337
                );
338
        }
339

340
        /**
341
         *  Creates a valid update URL for the Site Kit plugin.
342
         *
343
         * @return string
344
         */
345
        public function get_update_url(): string {
346
                return \html_entity_decode(
347
                        \wp_nonce_url(
348
                                \self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . self::SITE_KIT_FILE ),
349
                                'upgrade-plugin_' . self::SITE_KIT_FILE
350
                        )
351
                );
352
        }
353

354
        /**
355
         *  Creates a valid setup URL for the Site Kit plugin.
356
         *
357
         * @return string
358
         */
359
        public function get_setup_url(): string {
360
                return \self_admin_url( 'admin.php?page=googlesitekit-splash' );
361
        }
362
}
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