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

Yoast / wordpress-seo / 6638054992

25 Oct 2023 08:55AM UTC coverage: 49.106%. First build
6638054992

Pull #20653

github

vraja-pro
Merge remote-tracking branch 'origin/feature/upgrade-react-and-tests' into feature/upgrade-react-and-tests
Pull Request #20653: Feature/upgrade react and tests

64 of 157 new or added lines in 34 files covered. (40.76%)

13135 of 26748 relevant lines covered (49.11%)

3.96 hits per line

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

60.59
/inc/class-addon-manager.php
1
<?php
2
/**
3
 * WPSEO plugin file.
4
 *
5
 * @package WPSEO\Inc
6
 */
7

8
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
9

10
/**
11
 * Represents the addon manager.
12
 */
13
class WPSEO_Addon_Manager {
14

15
        /**
16
         * Holds the name of the transient.
17
         *
18
         * @var string
19
         */
20
        const SITE_INFORMATION_TRANSIENT = 'wpseo_site_information';
21

22
        /**
23
         * Holds the name of the transient.
24
         *
25
         * @var string
26
         */
27
        const SITE_INFORMATION_TRANSIENT_QUICK = 'wpseo_site_information_quick';
28

29
        /**
30
         * Holds the slug for YoastSEO free.
31
         *
32
         * @var string
33
         */
34
        const FREE_SLUG = 'yoast-seo-wordpress';
35

36
        /**
37
         * Holds the slug for YoastSEO Premium.
38
         *
39
         * @var string
40
         */
41
        const PREMIUM_SLUG = 'yoast-seo-wordpress-premium';
42

43
        /**
44
         * Holds the slug for Yoast News.
45
         *
46
         * @var string
47
         */
48
        const NEWS_SLUG = 'yoast-seo-news';
49

50
        /**
51
         * Holds the slug for Video.
52
         *
53
         * @var string
54
         */
55
        const VIDEO_SLUG = 'yoast-seo-video';
56

57
        /**
58
         * Holds the slug for WooCommerce.
59
         *
60
         * @var string
61
         */
62
        const WOOCOMMERCE_SLUG = 'yoast-seo-woocommerce';
63

64
        /**
65
         * Holds the slug for Local.
66
         *
67
         * @var string
68
         */
69
        const LOCAL_SLUG = 'yoast-seo-local';
70

71
        /**
72
         * The expected addon data.
73
         *
74
         * @var array
75
         */
76
        protected static $addons = [
77
                'wp-seo-premium.php'    => self::PREMIUM_SLUG,
78
                'wpseo-news.php'        => self::NEWS_SLUG,
79
                'video-seo.php'         => self::VIDEO_SLUG,
80
                'wpseo-woocommerce.php' => self::WOOCOMMERCE_SLUG,
81
                'local-seo.php'         => self::LOCAL_SLUG,
82
        ];
83

84
        /**
85
         * The addon data for the shortlinks.
86
         *
87
         * @var array
88
         */
89
        private $addon_details = [
90
                self::PREMIUM_SLUG     => [
91
                        'name'                  => 'Yoast SEO Premium',
92
                        'short_link_activation' => 'https://yoa.st/13j',
93
                        'short_link_renewal'    => 'https://yoa.st/4ey',
94
                ],
95
                self::NEWS_SLUG        => [
96
                        'name'                  => 'Yoast News SEO',
97
                        'short_link_activation' => 'https://yoa.st/4xq',
98
                        'short_link_renewal'    => 'https://yoa.st/4xv',
99
                ],
100
                self::WOOCOMMERCE_SLUG => [
101
                        'name'                  => 'Yoast WooCommerce SEO',
102
                        'short_link_activation' => 'https://yoa.st/4xs',
103
                        'short_link_renewal'    => 'https://yoa.st/4xx',
104
                ],
105
                self::VIDEO_SLUG       => [
106
                        'name'                  => 'Yoast Video SEO',
107
                        'short_link_activation' => 'https://yoa.st/4xr',
108
                        'short_link_renewal'    => 'https://yoa.st/4xw',
109
                ],
110
                self::LOCAL_SLUG       => [
111
                        'name'                  => 'Yoast Local SEO',
112
                        'short_link_activation' => 'https://yoa.st/4xp',
113
                        'short_link_renewal'    => 'https://yoa.st/4xu',
114
                ],
115
        ];
116

117
        /**
118
         * Holds the site information data.
119
         *
120
         * @var stdClass
121
         */
122
        private $site_information;
123

124
        /**
125
         * Hooks into WordPress.
126
         *
127
         * @codeCoverageIgnore
128
         *
129
         * @return void
130
         */
131
        public function register_hooks() {
132
                add_action( 'admin_init', [ $this, 'validate_addons' ], 15 );
133
                add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_updates' ] );
134
                add_filter( 'plugins_api', [ $this, 'get_plugin_information' ], 10, 3 );
135
                add_action( 'plugins_loaded', [ $this, 'register_expired_messages' ], 10 );
136
        }
137

138
        /**
139
         * Registers "expired subscription" warnings to the update messages of our addons.
140
         *
141
         * @return void
142
         */
143
        public function register_expired_messages() {
144
                foreach ( array_keys( $this->get_installed_addons() ) as $plugin_file ) {
×
145
                        add_action( 'in_plugin_update_message-' . $plugin_file, [ $this, 'expired_subscription_warning' ], 10, 2 );
×
146
                }
147
        }
148

149
        /**
150
         * Gets the subscriptions for current site.
151
         *
152
         * @return stdClass The subscriptions.
153
         */
154
        public function get_subscriptions() {
155
                return $this->get_site_information()->subscriptions;
8✔
156
        }
157

158
        /**
159
         * Provides a list of addon filenames.
160
         *
161
         * @return string[] List of addon filenames with their slugs.
162
         */
163
        public function get_addon_filenames() {
164
                return self::$addons;
2✔
165
        }
166

167
        /**
168
         * Finds the plugin file.
169
         *
170
         * @param string $plugin_slug The plugin slug to search.
171
         *
172
         * @return bool|string Plugin file when installed, False when plugin isn't installed.
173
         */
174
        public function get_plugin_file( $plugin_slug ) {
175
                $plugins            = $this->get_plugins();
4✔
176
                $plugin_files       = array_keys( $plugins );
4✔
177
                $target_plugin_file = array_search( $plugin_slug, $this->get_addon_filenames(), true );
4✔
178

179
                if ( ! $target_plugin_file ) {
4✔
180
                        return false;
2✔
181
                }
182

183
                foreach ( $plugin_files as $plugin_file ) {
2✔
184
                        if ( strpos( $plugin_file, $target_plugin_file ) !== false ) {
2✔
185
                                return $plugin_file;
2✔
186
                        }
187
                }
188

189
                return false;
×
190
        }
191

192
        /**
193
         * Retrieves the subscription for the given slug.
194
         *
195
         * @param string $slug The plugin slug to retrieve.
196
         *
197
         * @return stdClass|false Subscription data when found, false when not found.
198
         */
199
        public function get_subscription( $slug ) {
200
                foreach ( $this->get_subscriptions() as $subscription ) {
4✔
201
                        if ( $subscription->product->slug === $slug ) {
4✔
202
                                return $subscription;
2✔
203
                        }
204
                }
205

206
                return false;
2✔
207
        }
208

209
        /**
210
         * Retrieves a list of (subscription) slugs by the active addons.
211
         *
212
         * @return array The slugs.
213
         */
214
        public function get_subscriptions_for_active_addons() {
215
                $active_addons      = array_keys( $this->get_active_addons() );
2✔
216
                $subscription_slugs = array_map( [ $this, 'get_slug_by_plugin_file' ], $active_addons );
2✔
217
                $subscriptions      = [];
2✔
218
                foreach ( $subscription_slugs as $subscription_slug ) {
2✔
219
                        $subscriptions[ $subscription_slug ] = $this->get_subscription( $subscription_slug );
2✔
220
                }
221

222
                return $subscriptions;
2✔
223
        }
224

225
        /**
226
         * Retrieves a list of versions for each addon.
227
         *
228
         * @return array The addon versions.
229
         */
230
        public function get_installed_addons_versions() {
231
                $addon_versions = [];
2✔
232
                foreach ( $this->get_installed_addons() as $plugin_file => $installed_addon ) {
2✔
233
                        $addon_versions[ $this->get_slug_by_plugin_file( $plugin_file ) ] = $installed_addon['Version'];
2✔
234
                }
235

236
                return $addon_versions;
2✔
237
        }
238

239
        /**
240
         * Retrieves the plugin information from the subscriptions.
241
         *
242
         * @param stdClass|false $data   The result object. Default false.
243
         * @param string         $action The type of information being requested from the Plugin Installation API.
244
         * @param stdClass       $args   Plugin API arguments.
245
         *
246
         * @return object Extended plugin data.
247
         */
248
        public function get_plugin_information( $data, $action, $args ) {
249
                if ( $action !== 'plugin_information' ) {
8✔
250
                        return $data;
2✔
251
                }
252

253
                if ( ! isset( $args->slug ) ) {
6✔
254
                        return $data;
2✔
255
                }
256

257
                $subscription = $this->get_subscription( $args->slug );
4✔
258
                if ( ! $subscription ) {
4✔
259
                        return $data;
2✔
260
                }
261

262
                $data = $this->convert_subscription_to_plugin( $subscription, null, true );
2✔
263

264
                if ( $this->has_subscription_expired( $subscription ) ) {
2✔
265
                        unset( $data->package, $data->download_link );
×
266
                }
267

268
                return $data;
2✔
269
        }
270

271
        /**
272
         * Retrieves information from MyYoast about which addons are connected to the current site.
273
         *
274
         * @return stdClass The list of addons activated for this site.
275
         */
276
        public function get_myyoast_site_information() {
277
                if ( $this->site_information === null ) {
10✔
278
                        $this->site_information = $this->get_site_information_transient();
10✔
279
                }
280

281
                if ( $this->site_information ) {
10✔
282
                        return $this->site_information;
10✔
283
                }
284

285
                $this->site_information = $this->request_current_sites();
×
286
                if ( $this->site_information ) {
×
287
                        $this->site_information = $this->map_site_information( $this->site_information );
×
288

289
                        $this->set_site_information_transient( $this->site_information );
×
290

291
                        return $this->site_information;
×
292
                }
293

294
                return $this->get_site_information_default();
×
295
        }
296

297
        /**
298
         * Checks if the subscription for the given slug is valid.
299
         *
300
         * @param string $slug The plugin slug to retrieve.
301
         *
302
         * @return bool True when the subscription is valid.
303
         */
304
        public function has_valid_subscription( $slug ) {
305
                $subscription = $this->get_subscription( $slug );
6✔
306

307
                // An non-existing subscription is never valid.
308
                if ( ! $subscription ) {
6✔
309
                        return false;
2✔
310
                }
311

312
                return ! $this->has_subscription_expired( $subscription );
4✔
313
        }
314

315
        /**
316
         * Checks if there are addon updates.
317
         *
318
         * @param stdClass|mixed $data The current data for update_plugins.
319
         *
320
         * @return stdClass Extended data for update_plugins.
321
         */
322
        public function check_for_updates( $data ) {
323
                global $wp_version;
14✔
324

325
                if ( empty( $data ) ) {
14✔
326
                        return $data;
6✔
327
                }
328

329
                // We have to figure out if we're safe to upgrade the add-ons, based on what the latest Yoast Free requirements for the WP version is.
330
                $yoast_free_data = $this->extract_yoast_data( $data );
8✔
331

332
                foreach ( $this->get_installed_addons() as $plugin_file => $installed_plugin ) {
8✔
333
                        $subscription_slug = $this->get_slug_by_plugin_file( $plugin_file );
6✔
334
                        $subscription      = $this->get_subscription( $subscription_slug );
6✔
335

336
                        if ( ! $subscription ) {
6✔
337
                                continue;
4✔
338
                        }
339

340
                        $plugin_data = $this->convert_subscription_to_plugin( $subscription, $yoast_free_data, false, $plugin_file );
2✔
341

342
                        // Let's assume for now that it will get added in the 'no_update' key that we'll return to the WP API.
343
                        $is_no_update = true;
2✔
344

345
                        // If the add-on's version is the latest, we have to do no further checks.
346
                        if ( version_compare( $installed_plugin['Version'], $plugin_data->new_version, '<' ) ) {
2✔
347
                                // If we haven't retrieved the Yoast Free requirements for the WP version yet, do nothing. The next run will probably get us that information.
348
                                if ( is_null( $plugin_data->requires ) ) {
2✔
349
                                        continue;
×
350
                                }
351

352
                                if ( version_compare( $plugin_data->requires, $wp_version, '<=' ) ) {
2✔
353
                                        // The add-on has an available update *and* the Yoast Free requirements for the WP version are also met, so go ahead and show the upgrade info to the user.
354
                                        $is_no_update                   = false;
2✔
355
                                        $data->response[ $plugin_file ] = $plugin_data;
2✔
356

357
                                        if ( $this->has_subscription_expired( $subscription ) ) {
2✔
358
                                                unset( $data->response[ $plugin_file ]->package, $data->response[ $plugin_file ]->download_link );
×
359
                                        }
360
                                }
361
                        }
362

363
                        if ( $is_no_update ) {
2✔
364
                                // Still convert subscription when no updates is available.
365
                                $data->no_update[ $plugin_file ] = $plugin_data;
2✔
366

367
                                if ( $this->has_subscription_expired( $subscription ) ) {
2✔
368
                                        unset( $data->no_update[ $plugin_file ]->package, $data->no_update[ $plugin_file ]->download_link );
×
369
                                }
370
                        }
371
                }
372

373
                return $data;
8✔
374
        }
375

376
        /**
377
         * Extracts Yoast SEO Free's data from the wp.org API response.
378
         *
379
         * @param object $data The wp.org API response.
380
         *
381
         * @return object Yoast Free's data from wp.org.
382
         */
383
        protected function extract_yoast_data( $data ) {
384
                if ( isset( $data->response[ WPSEO_BASENAME ] ) ) {
×
385
                        return $data->response[ WPSEO_BASENAME ];
×
386
                }
387

388
                if ( isset( $data->no_update[ WPSEO_BASENAME ] ) ) {
×
389
                        return $data->no_update[ WPSEO_BASENAME ];
×
390
                }
391

392
                return (object) [];
×
393
        }
394

395
        /**
396
         * If the plugin is lacking an active subscription, throw a warning.
397
         *
398
         * @param array $plugin_data The data for the plugin in this row.
399
         */
400
        public function expired_subscription_warning( $plugin_data ) {
401
                $subscription = $this->get_subscription( $plugin_data['slug'] );
×
402
                if ( $subscription && $this->has_subscription_expired( $subscription ) ) {
×
403
                        $addon_link = ( isset( $this->addon_details[ $plugin_data['slug'] ] ) ) ? $this->addon_details[ $plugin_data['slug'] ]['short_link_renewal'] : $this->addon_details[ self::PREMIUM_SLUG ]['short_link_renewal'];
×
404

NEW
405
                        $sale_copy = '';
×
NEW
406
                        if ( YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2023-promotion' ) ) {
×
NEW
407
                                $sale_copy = sprintf(
×
408
                                /* translators: %1$s is a <br> tag. */
NEW
409
                                        esc_html__( '%1$s Now with 30%% Black Friday Discount!', 'wordpress-seo' ),
×
NEW
410
                                        '<br>'
×
411
                                );
412
                        }
413
                        echo '<br><br>';
×
414
                        echo '<strong><span class="yoast-dashicons-notice warning dashicons dashicons-warning"></span> ' .
415
                                sprintf(
×
416
                                        /* translators: %1$s is the plugin name, %2$s and %3$s are a link. */
417
                                        esc_html__( '%1$s can\'t be updated because your product subscription is expired. %2$sRenew your product subscription%3$s to get updates again and use all the features of %1$s.', 'wordpress-seo' ),
×
418
                                        esc_html( $plugin_data['name'] ),
×
419
                                        '<a href="' . esc_url( WPSEO_Shortlinker::get( $addon_link ) ) . '">',
×
420
                                        '</a>'
×
421
                                ) .
422
                            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped above.
NEW
423
                                $sale_copy
×
NEW
424
                                . '</strong>';
×
425
                }
426
        }
427

428
        /**
429
         * Checks if there are any installed addons.
430
         *
431
         * @return bool True when there are installed Yoast addons.
432
         */
433
        public function has_installed_addons() {
434
                $installed_addons = $this->get_installed_addons();
2✔
435

436
                return ! empty( $installed_addons );
2✔
437
        }
438

439
        /**
440
         * Checks if the plugin is installed and activated in WordPress.
441
         *
442
         * @param string $slug The class' slug.
443
         *
444
         * @return bool True when installed and activated.
445
         */
446
        public function is_installed( $slug ) {
447
                $slug_to_class_map = [
448
                        static::PREMIUM_SLUG     => 'WPSEO_Premium',
×
449
                        static::NEWS_SLUG        => 'WPSEO_News',
×
450
                        static::WOOCOMMERCE_SLUG => 'Yoast_WooCommerce_SEO',
×
451
                        static::VIDEO_SLUG       => 'WPSEO_Video_Sitemap',
×
452
                        static::LOCAL_SLUG       => 'WPSEO_Local_Core',
×
453
                ];
454

455
                if ( ! isset( $slug_to_class_map[ $slug ] ) ) {
×
456
                        return false;
×
457
                }
458

459
                return class_exists( $slug_to_class_map[ $slug ] );
×
460
        }
461

462
        /**
463
         * Validates the addons and show a notice for the ones that are invalid.
464
         */
465
        public function validate_addons() {
466
                $notification_center = Yoast_Notification_Center::get();
×
467

468
                if ( $notification_center === null ) {
×
469
                        return;
×
470
                }
471

472
                foreach ( $this->addon_details as $slug => $addon_info ) {
×
473
                        $notification = $this->create_notification( $addon_info['name'], $addon_info['short_link_activation'] );
×
474

475
                        // Add a notification when the installed plugin isn't activated in My Yoast.
476
                        if ( $this->is_installed( $slug ) && ! $this->has_valid_subscription( $slug ) ) {
×
477
                                $notification_center->add_notification( $notification );
×
478

479
                                continue;
×
480
                        }
481

482
                        $notification_center->remove_notification( $notification );
×
483
                }
484
        }
485

486
        /**
487
         * Removes the site information transients.
488
         *
489
         * @codeCoverageIgnore
490
         *
491
         * @return void
492
         */
493
        public function remove_site_information_transients() {
494
                delete_transient( self::SITE_INFORMATION_TRANSIENT );
495
                delete_transient( self::SITE_INFORMATION_TRANSIENT_QUICK );
496
        }
497

498
        /**
499
         * Creates an instance of Yoast_Notification.
500
         *
501
         * @param string $product_name The product to create the notification for.
502
         * @param string $short_link   The short link for the addon notification.
503
         *
504
         * @return Yoast_Notification The created notification.
505
         */
506
        protected function create_notification( $product_name, $short_link ) {
507
                $notification_options = [
4✔
508
                        'type'         => Yoast_Notification::ERROR,
8✔
509
                        'id'           => 'wpseo-dismiss-' . sanitize_title_with_dashes( $product_name, null, 'save' ),
8✔
510
                        'capabilities' => 'wpseo_manage_options',
8✔
511
                ];
4✔
512

513
                return new Yoast_Notification(
8✔
514
                        sprintf(
8✔
515
                        /* translators: %1$s expands to a strong tag, %2$s expands to the product name, %3$s expands to a closing strong tag, %4$s expands to an a tag. %5$s expands to MyYoast, %6$s expands to a closing a tag,  %7$s expands to the product name  */
516
                                __( '%1$s %2$s isn\'t working as expected %3$s and you are not receiving updates or support! Make sure to %4$s activate your product subscription in %5$s%6$s to unlock all the features of %7$s.', 'wordpress-seo' ),
8✔
517
                                '<strong>',
8✔
518
                                $product_name,
8✔
519
                                '</strong>',
8✔
520
                                '<a href="' . WPSEO_Shortlinker::get( $short_link ) . '" target="_blank">',
8✔
521
                                'MyYoast',
8✔
522
                                '</a>',
8✔
523
                                $product_name
8✔
524
                        ),
4✔
525
                        $notification_options
8✔
526
                );
4✔
527
        }
528

529
        /**
530
         * Checks whether a plugin expiry date has been passed.
531
         *
532
         * @param stdClass $subscription Plugin subscription.
533
         *
534
         * @return bool Has the plugin expired.
535
         */
536
        protected function has_subscription_expired( $subscription ) {
537
                return ( strtotime( $subscription->expiry_date ) - time() ) < 0;
×
538
        }
539

540
        /**
541
         * Converts a subscription to plugin based format.
542
         *
543
         * @param stdClass      $subscription    The subscription to convert.
544
         * @param stdClass|null $yoast_free_data The Yoast Free's data.
545
         * @param bool          $plugin_info     Whether we're in the plugin information modal.
546
         * @param string        $plugin_file     The plugin filename.
547
         *
548
         * @return stdClass The converted subscription.
549
         */
550
        protected function convert_subscription_to_plugin( $subscription, $yoast_free_data = null, $plugin_info = false, $plugin_file = '' ) {
551
                // We need to replace h2's and h3's with h4's because the styling expects that.
552
                $changelog = str_replace( '</h2', '</h4', str_replace( '<h2', '<h4', $subscription->product->changelog ) );
2✔
553
                $changelog = str_replace( '</h3', '</h4', str_replace( '<h3', '<h4', $changelog ) );
2✔
554

555
                // If we're running this because we want to just show the plugin info in the version details modal, we can fallback to the Yoast Free constants, since that modal will not be accessible anyway in the event that the new Free version increases those constants.
556
                $defaults = [
1✔
557
                        // It can be expanded if we have the 'tested' and 'requires_php' data be returned from wp.org in the future.
558
                        'requires'     => ( $plugin_info ) ? YOAST_SEO_WP_REQUIRED : null,
2✔
559
                ];
1✔
560

561
                return (object) [
1✔
562
                        'new_version'      => $subscription->product->version,
2✔
563
                        'name'             => $subscription->product->name,
2✔
564
                        'slug'             => $subscription->product->slug,
2✔
565
                        'plugin'           => $plugin_file,
2✔
566
                        'url'              => $subscription->product->store_url,
2✔
567
                        'last_update'      => $subscription->product->last_updated,
2✔
568
                        'homepage'         => $subscription->product->store_url,
2✔
569
                        'download_link'    => $subscription->product->download,
2✔
570
                        'package'          => $subscription->product->download,
2✔
571
                        'sections'         => [
1✔
572
                                'changelog' => $changelog,
2✔
573
                                'support'   => $this->get_support_section(),
2✔
574
                        ],
1✔
575
                        'icons'            => [
1✔
576
                                '2x' => $this->get_icon( $subscription->product->slug ),
2✔
577
                        ],
1✔
578
                        'update_supported' => true,
1✔
579
                        'banners'          => $this->get_banners( $subscription->product->slug ),
2✔
580
                        // If we have extracted Yoast Free's data before, use that. If not, resort to the defaults.
581
                        'tested'           => YOAST_SEO_WP_TESTED,
1✔
582
                        'requires'         => isset( $yoast_free_data->requires ) ? $yoast_free_data->requires : $defaults['requires'],
2✔
583
                        'requires_php'     => YOAST_SEO_PHP_REQUIRED,
1✔
584
                ];
1✔
585
        }
586

587
        /**
588
         * Returns the plugin's icon URL.
589
         *
590
         * @param string $slug The plugin slug.
591
         *
592
         * @return string The icon URL for this plugin.
593
         */
594
        protected function get_icon( $slug ) {
595
                switch ( $slug ) {
596
                        case self::LOCAL_SLUG:
597
                                return 'https://yoa.st/local-seo-icon';
×
598
                        case self::NEWS_SLUG:
599
                                return 'https://yoa.st/news-seo-icon';
×
600
                        case self::PREMIUM_SLUG:
601
                                return 'https://yoa.st/yoast-seo-icon';
×
602
                        case self::VIDEO_SLUG:
603
                                return 'https://yoa.st/video-seo-icon';
×
604
                        case self::WOOCOMMERCE_SLUG:
605
                                return 'https://yoa.st/woo-seo-icon';
×
606
                }
607
        }
608

609
        /**
610
         * Return an array of plugin banner URLs.
611
         *
612
         * @param string $slug The plugin slug.
613
         *
614
         * @return string[]
615
         */
616
        protected function get_banners( $slug ) {
617
                switch ( $slug ) {
618
                        case self::LOCAL_SLUG:
619
                                return [
620
                                        'high' => 'https://yoa.st/yoast-seo-banner-local',
×
621
                                        'low'  => 'https://yoa.st/yoast-seo-banner-low-local',
622
                                ];
623
                        case self::NEWS_SLUG:
624
                                return [
625
                                        'high' => 'https://yoa.st/yoast-seo-banner-news',
×
626
                                        'low'  => 'https://yoa.st/yoast-seo-banner-low-news',
627
                                ];
628
                        case self::PREMIUM_SLUG:
629
                                return [
630
                                        'high' => 'https://yoa.st/yoast-seo-banner-premium',
×
631
                                        'low'  => 'https://yoa.st/yoast-seo-banner-low-premium',
632
                                ];
633
                        case self::VIDEO_SLUG:
634
                                return [
635
                                        'high' => 'https://yoa.st/yoast-seo-banner-video',
×
636
                                        'low'  => 'https://yoa.st/yoast-seo-banner-low-video',
637
                                ];
638
                        case self::WOOCOMMERCE_SLUG:
639
                                return [
640
                                        'high' => 'https://yoa.st/yoast-seo-banner-woo',
×
641
                                        'low'  => 'https://yoa.st/yoast-seo-banner-low-woo',
642
                                ];
643
                }
644
        }
645

646
        /**
647
         * Checks if the given plugin_file belongs to a Yoast addon.
648
         *
649
         * @param string $plugin_file Path to the plugin.
650
         *
651
         * @return bool True when plugin file is for a Yoast addon.
652
         */
653
        protected function is_yoast_addon( $plugin_file ) {
654
                return $this->get_slug_by_plugin_file( $plugin_file ) !== '';
2✔
655
        }
656

657
        /**
658
         * Retrieves the addon slug by given plugin file path.
659
         *
660
         * @param string $plugin_file The file path to the plugin.
661
         *
662
         * @return string The slug when found or empty string when not.
663
         */
664
        protected function get_slug_by_plugin_file( $plugin_file ) {
665
                $addons = self::$addons;
2✔
666

667
                // Yoast SEO Free isn't an addon, but we needed it in Premium to fetch translations.
668
                if ( YoastSEO()->helpers->product->is_premium() ) {
2✔
669
                        $addons['wp-seo.php'] = self::FREE_SLUG;
×
670
                }
671

672
                foreach ( $addons as $addon => $addon_slug ) {
2✔
673
                        if ( strpos( $plugin_file, $addon ) !== false ) {
2✔
674
                                return $addon_slug;
2✔
675
                        }
676
                }
677

678
                return '';
2✔
679
        }
680

681
        /**
682
         * Retrieves the installed Yoast addons.
683
         *
684
         * @return array The installed plugins.
685
         */
686
        protected function get_installed_addons() {
687
                return array_filter( $this->get_plugins(), [ $this, 'is_yoast_addon' ], ARRAY_FILTER_USE_KEY );
4✔
688
        }
689

690
        /**
691
         * Retrieves a list of active addons.
692
         *
693
         * @return array The active addons.
694
         */
695
        protected function get_active_addons() {
696
                return array_filter( $this->get_installed_addons(), [ $this, 'is_plugin_active' ], ARRAY_FILTER_USE_KEY );
2✔
697
        }
698

699
        /**
700
         * Retrieves the current sites from the API.
701
         *
702
         * @codeCoverageIgnore
703
         *
704
         * @return bool|stdClass Object when request is successful. False if not.
705
         */
706
        protected function request_current_sites() {
707
                $api_request = new WPSEO_MyYoast_Api_Request( 'sites/current' );
708
                if ( $api_request->fire() ) {
709
                        return $api_request->get_response();
710
                }
711

712
                return $this->get_site_information_default();
713
        }
714

715
        /**
716
         * Retrieves the transient value with the site information.
717
         *
718
         * @codeCoverageIgnore
719
         *
720
         * @return stdClass|false The transient value.
721
         */
722
        protected function get_site_information_transient() {
723
                global $pagenow;
724

725
                // Force re-check on license & dashboard pages.
726
                $current_page = null;
727
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
728
                if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) ) {
729
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing and thus no need to sanitize.
730
                        $current_page = wp_unslash( $_GET['page'] );
731
                }
732

733
                // Check whether the licenses are valid or whether we need to show notifications.
734
                $quick = ( $current_page === 'wpseo_licenses' || $current_page === 'wpseo_dashboard' );
735

736
                // Also do a fresh request on Plugins & Core Update pages.
737
                $quick = $quick || $pagenow === 'plugins.php';
738
                $quick = $quick || $pagenow === 'update-core.php';
739

740
                if ( $quick ) {
741
                        return get_transient( self::SITE_INFORMATION_TRANSIENT_QUICK );
742
                }
743

744
                return get_transient( self::SITE_INFORMATION_TRANSIENT );
745
        }
746

747
        /**
748
         * Sets the site information transient.
749
         *
750
         * @codeCoverageIgnore
751
         *
752
         * @param stdClass $site_information The site information to save.
753
         *
754
         * @return void
755
         */
756
        protected function set_site_information_transient( $site_information ) {
757
                set_transient( self::SITE_INFORMATION_TRANSIENT, $site_information, DAY_IN_SECONDS );
758
                set_transient( self::SITE_INFORMATION_TRANSIENT_QUICK, $site_information, 60 );
759
        }
760

761
        /**
762
         * Retrieves all installed WordPress plugins.
763
         *
764
         * @codeCoverageIgnore
765
         *
766
         * @return array The plugins.
767
         */
768
        protected function get_plugins() {
769
                if ( ! function_exists( 'get_plugins' ) ) {
770
                        require_once ABSPATH . 'wp-admin/includes/plugin.php';
771
                }
772

773
                return get_plugins();
774
        }
775

776
        /**
777
         * Checks if the given plugin file belongs to an active plugin.
778
         *
779
         * @codeCoverageIgnore
780
         *
781
         * @param string $plugin_file The file path to the plugin.
782
         *
783
         * @return bool True when plugin is active.
784
         */
785
        protected function is_plugin_active( $plugin_file ) {
786
                return is_plugin_active( $plugin_file );
787
        }
788

789
        /**
790
         * Returns an object with no subscriptions.
791
         *
792
         * @codeCoverageIgnore
793
         *
794
         * @return stdClass Site information.
795
         */
796
        protected function get_site_information_default() {
797
                return (object) [
798
                        'url'           => WPSEO_Utils::get_home_url(),
799
                        'subscriptions' => [],
800
                ];
801
        }
802

803
        /**
804
         * Maps the plugin API response.
805
         *
806
         * @param object $site_information Site information as received from the API.
807
         *
808
         * @return stdClass Mapped site information.
809
         */
810
        protected function map_site_information( $site_information ) {
811
                return (object) [
812
                        'url'           => $site_information->url,
×
813
                        'subscriptions' => array_map( [ $this, 'map_subscription' ], $site_information->subscriptions ),
×
814
                ];
815
        }
816

817
        /**
818
         * Maps a plugin subscription.
819
         *
820
         * @param object $subscription Subscription information as received from the API.
821
         *
822
         * @return stdClass Mapped subscription.
823
         */
824
        protected function map_subscription( $subscription ) {
825
                // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Not our properties.
826
                return (object) [
827
                        'renewal_url' => $subscription->renewalUrl,
×
828
                        'expiry_date' => $subscription->expiryDate,
×
829
                        'product'     => (object) [
830
                                'version'      => $subscription->product->version,
×
831
                                'name'         => $subscription->product->name,
×
832
                                'slug'         => $subscription->product->slug,
×
833
                                'last_updated' => $subscription->product->lastUpdated,
×
834
                                'store_url'    => $subscription->product->storeUrl,
×
835
                                // Ternary operator is necessary because download can be undefined.
836
                                'download'     => isset( $subscription->product->download ) ? $subscription->product->download : null,
×
837
                                'changelog'    => $subscription->product->changelog,
×
838
                        ],
839
                ];
840
                // phpcs:enable
841
        }
842

843
        /**
844
         * Retrieves the site information.
845
         *
846
         * @return stdClass The site information.
847
         */
848
        private function get_site_information() {
849
                if ( ! $this->has_installed_addons() ) {
8✔
850
                        return $this->get_site_information_default();
2✔
851
                }
852

853
                return $this->get_myyoast_site_information();
6✔
854
        }
855

856
        /**
857
         * Retrieves the contents for the support section.
858
         *
859
         * @return string The support section content.
860
         */
861
        protected function get_support_section() {
862
                return '<h4>' . __( 'Need support?', 'wordpress-seo' ) . '</h4>'
×
863
                        . '<p>'
×
864
                        /* translators: 1: expands to <a> that refers to the help page, 2: </a> closing tag. */
865
                        . sprintf( __( 'You can probably find an answer to your question in our %1$shelp center%2$s.', 'wordpress-seo' ), '<a href="https://yoast.com/help/">', '</a>' )
×
866
                        . ' '
×
867
                        /* translators: %s expands to a mailto support link. */
868
                        . sprintf( __( 'If you still need support and have an active subscription for this product, please email %s.', 'wordpress-seo' ), '<a href="mailto:support@yoast.com">support@yoast.com</a>' )
×
869
                        . '</p>';
×
870
        }
871
}
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