PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.1
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 / inc / class-addon-manager.php

class-addon-manager.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.1, at inc/class-addon-manager.php

826 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Inc
6 */
7
8 /**
9 * Represents the addon manager.
10 */
11 class WPSEO_Addon_Manager {
12
13 /**
14 * Holds the name of the transient.
15 *
16 * @var string
17 */
18 const SITE_INFORMATION_TRANSIENT = 'wpseo_site_information';
19
20 /**
21 * Holds the name of the transient.
22 *
23 * @var string
24 */
25 const SITE_INFORMATION_TRANSIENT_QUICK = 'wpseo_site_information_quick';
26
27 /**
28 * Holds the slug for YoastSEO free.
29 *
30 * @var string
31 */
32 const FREE_SLUG = 'yoast-seo-wordpress';
33
34 /**
35 * Holds the slug for YoastSEO Premium.
36 *
37 * @var string
38 */
39 const PREMIUM_SLUG = 'yoast-seo-wordpress-premium';
40
41 /**
42 * Holds the slug for Yoast News.
43 *
44 * @var string
45 */
46 const NEWS_SLUG = 'yoast-seo-news';
47
48 /**
49 * Holds the slug for Video.
50 *
51 * @var string
52 */
53 const VIDEO_SLUG = 'yoast-seo-video';
54
55 /**
56 * Holds the slug for WooCommerce.
57 *
58 * @var string
59 */
60 const WOOCOMMERCE_SLUG = 'yoast-seo-woocommerce';
61
62 /**
63 * Holds the slug for Local.
64 *
65 * @var string
66 */
67 const LOCAL_SLUG = 'yoast-seo-local';
68
69 /**
70 * The expected addon data.
71 *
72 * @var array
73 */
74 protected static $addons = [
75 'wp-seo-premium.php' => self::PREMIUM_SLUG,
76 'wpseo-news.php' => self::NEWS_SLUG,
77 'video-seo.php' => self::VIDEO_SLUG,
78 'wpseo-woocommerce.php' => self::WOOCOMMERCE_SLUG,
79 'local-seo.php' => self::LOCAL_SLUG,
80 ];
81
82 /**
83 * Holds the site information data.
84 *
85 * @var stdClass
86 */
87 private $site_information;
88
89 /**
90 * Hooks into WordPress.
91 *
92 * @codeCoverageIgnore
93 *
94 * @return void
95 */
96 public function register_hooks() {
97 add_action( 'admin_init', [ $this, 'validate_addons' ], 15 );
98 add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_updates' ] );
99 add_filter( 'plugins_api', [ $this, 'get_plugin_information' ], 10, 3 );
100 add_action( 'plugins_loaded', [ $this, 'register_expired_messages' ], 10 );
101 }
102
103 /**
104 * Registers "expired subscription" warnings to the update messages of our addons.
105 *
106 * @return void
107 */
108 public function register_expired_messages() {
109 foreach ( array_keys( $this->get_installed_addons() ) as $plugin_file ) {
110 add_action( 'in_plugin_update_message-' . $plugin_file, [ $this, 'expired_subscription_warning' ], 10, 2 );
111 }
112 }
113
114 /**
115 * Gets the subscriptions for current site.
116 *
117 * @return stdClass The subscriptions.
118 */
119 public function get_subscriptions() {
120 return $this->get_site_information()->subscriptions;
121 }
122
123 /**
124 * Provides a list of addon filenames.
125 *
126 * @return string[] List of addon filenames with their slugs.
127 */
128 public function get_addon_filenames() {
129 return self::$addons;
130 }
131
132 /**
133 * Finds the plugin file.
134 *
135 * @param string $plugin_slug The plugin slug to search.
136 *
137 * @return bool|string Plugin file when installed, False when plugin isn't installed.
138 */
139 public function get_plugin_file( $plugin_slug ) {
140 $plugins = $this->get_plugins();
141 $plugin_files = array_keys( $plugins );
142 $target_plugin_file = array_search( $plugin_slug, $this->get_addon_filenames(), true );
143
144 if ( ! $target_plugin_file ) {
145 return false;
146 }
147
148 foreach ( $plugin_files as $plugin_file ) {
149 if ( strpos( $plugin_file, $target_plugin_file ) !== false ) {
150 return $plugin_file;
151 }
152 }
153
154 return false;
155 }
156
157 /**
158 * Retrieves the subscription for the given slug.
159 *
160 * @param string $slug The plugin slug to retrieve.
161 *
162 * @return stdClass|false Subscription data when found, false when not found.
163 */
164 public function get_subscription( $slug ) {
165 foreach ( $this->get_subscriptions() as $subscription ) {
166 if ( $subscription->product->slug === $slug ) {
167 return $subscription;
168 }
169 }
170
171 return false;
172 }
173
174 /**
175 * Retrieves a list of (subscription) slugs by the active addons.
176 *
177 * @return array The slugs.
178 */
179 public function get_subscriptions_for_active_addons() {
180 $active_addons = array_keys( $this->get_active_addons() );
181 $subscription_slugs = array_map( [ $this, 'get_slug_by_plugin_file' ], $active_addons );
182 $subscriptions = [];
183 foreach ( $subscription_slugs as $subscription_slug ) {
184 $subscriptions[ $subscription_slug ] = $this->get_subscription( $subscription_slug );
185 }
186
187 return $subscriptions;
188 }
189
190 /**
191 * Retrieves a list of versions for each addon.
192 *
193 * @return array The addon versions.
194 */
195 public function get_installed_addons_versions() {
196 $addon_versions = [];
197 foreach ( $this->get_installed_addons() as $plugin_file => $installed_addon ) {
198 $addon_versions[ $this->get_slug_by_plugin_file( $plugin_file ) ] = $installed_addon['Version'];
199 }
200
201 return $addon_versions;
202 }
203
204 /**
205 * Retrieves the plugin information from the subscriptions.
206 *
207 * @param stdClass|false $data The result object. Default false.
208 * @param string $action The type of information being requested from the Plugin Installation API.
209 * @param stdClass $args Plugin API arguments.
210 *
211 * @return object Extended plugin data.
212 */
213 public function get_plugin_information( $data, $action, $args ) {
214 if ( $action !== 'plugin_information' ) {
215 return $data;
216 }
217
218 if ( ! isset( $args->slug ) ) {
219 return $data;
220 }
221
222 $subscription = $this->get_subscription( $args->slug );
223 if ( ! $subscription ) {
224 return $data;
225 }
226
227 $data = $this->convert_subscription_to_plugin( $subscription, null, true );
228
229 if ( $this->has_subscription_expired( $subscription ) ) {
230 unset( $data->package, $data->download_link );
231 }
232
233 return $data;
234 }
235
236 /**
237 * Retrieves information from MyYoast about which addons are connected to the current site.
238 *
239 * @return stdClass The list of addons activated for this site.
240 */
241 public function get_myyoast_site_information() {
242 if ( $this->site_information === null ) {
243 $this->site_information = $this->get_site_information_transient();
244 }
245
246 if ( $this->site_information ) {
247 return $this->site_information;
248 }
249
250 $this->site_information = $this->request_current_sites();
251 if ( $this->site_information ) {
252 $this->site_information = $this->map_site_information( $this->site_information );
253
254 $this->set_site_information_transient( $this->site_information );
255
256 return $this->site_information;
257 }
258
259 return $this->get_site_information_default();
260 }
261
262 /**
263 * Checks if the subscription for the given slug is valid.
264 *
265 * @param string $slug The plugin slug to retrieve.
266 *
267 * @return bool True when the subscription is valid.
268 */
269 public function has_valid_subscription( $slug ) {
270 $subscription = $this->get_subscription( $slug );
271
272 // An non-existing subscription is never valid.
273 if ( ! $subscription ) {
274 return false;
275 }
276
277 return ! $this->has_subscription_expired( $subscription );
278 }
279
280 /**
281 * Checks if there are addon updates.
282 *
283 * @param stdClass|mixed $data The current data for update_plugins.
284 *
285 * @return stdClass Extended data for update_plugins.
286 */
287 public function check_for_updates( $data ) {
288 global $wp_version;
289
290 if ( empty( $data ) ) {
291 return $data;
292 }
293
294 // 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.
295 $yoast_free_data = $this->extract_yoast_data( $data );
296
297 foreach ( $this->get_installed_addons() as $plugin_file => $installed_plugin ) {
298 $subscription_slug = $this->get_slug_by_plugin_file( $plugin_file );
299 $subscription = $this->get_subscription( $subscription_slug );
300
301 if ( ! $subscription ) {
302 continue;
303 }
304
305 $plugin_data = $this->convert_subscription_to_plugin( $subscription, $yoast_free_data, false, $plugin_file );
306
307 // Let's assume for now that it will get added in the 'no_update' key that we'll return to the WP API.
308 $is_no_update = true;
309
310 // If the add-on's version is the latest, we have to do no further checks.
311 if ( version_compare( $installed_plugin['Version'], $plugin_data->new_version, '<' ) ) {
312 // 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.
313 if ( is_null( $plugin_data->requires ) ) {
314 continue;
315 }
316
317 if ( version_compare( $plugin_data->requires, $wp_version, '<=' ) ) {
318 // 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.
319 $is_no_update = false;
320 $data->response[ $plugin_file ] = $plugin_data;
321
322 if ( $this->has_subscription_expired( $subscription ) ) {
323 unset( $data->response[ $plugin_file ]->package, $data->response[ $plugin_file ]->download_link );
324 }
325 }
326 }
327
328 if ( $is_no_update ) {
329 // Still convert subscription when no updates is available.
330 $data->no_update[ $plugin_file ] = $plugin_data;
331
332 if ( $this->has_subscription_expired( $subscription ) ) {
333 unset( $data->no_update[ $plugin_file ]->package, $data->no_update[ $plugin_file ]->download_link );
334 }
335 }
336 }
337
338 return $data;
339 }
340
341 /**
342 * Extracts Yoast SEO Free's data from the wp.org API response.
343 *
344 * @param object $data The wp.org API response.
345 *
346 * @return object Yoast Free's data from wp.org.
347 */
348 protected function extract_yoast_data( $data ) {
349 if ( isset( $data->response[ WPSEO_BASENAME ] ) ) {
350 return $data->response[ WPSEO_BASENAME ];
351 }
352
353 if ( isset( $data->no_update[ WPSEO_BASENAME ] ) ) {
354 return $data->no_update[ WPSEO_BASENAME ];
355 }
356
357 return (object) [];
358 }
359
360 /**
361 * If the plugin is lacking an active subscription, throw a warning.
362 *
363 * @param array $plugin_data The data for the plugin in this row.
364 */
365 public function expired_subscription_warning( $plugin_data ) {
366 $subscription = $this->get_subscription( $plugin_data['slug'] );
367 if ( $subscription && $this->has_subscription_expired( $subscription ) ) {
368 echo '<br><br>';
369 /* translators: %1$s is the plugin name, %2$s and %3$s are a link. */
370 echo '<strong><span class="wp-ui-text-notification alert dashicons dashicons-warning"></span> ' . sprintf( esc_html__( 'A new version of %1$s is available. %2$sRenew your subscription%3$s if you want to update to the latest version.', 'wordpress-seo' ), esc_html( $plugin_data['name'] ), '<a href="' . esc_attr( WPSEO_Shortlinker::get( 'https://yoa.st/4ey' ) ) . '">', '</a>' ) . '</strong>';
371 }
372 }
373
374 /**
375 * Checks if there are any installed addons.
376 *
377 * @return bool True when there are installed Yoast addons.
378 */
379 public function has_installed_addons() {
380 $installed_addons = $this->get_installed_addons();
381
382 return ! empty( $installed_addons );
383 }
384
385 /**
386 * Checks if the plugin is installed and activated in WordPress.
387 *
388 * @param string $slug The class' slug.
389 *
390 * @return bool True when installed and activated.
391 */
392 public function is_installed( $slug ) {
393 $slug_to_class_map = [
394 static::PREMIUM_SLUG => 'WPSEO_Premium',
395 static::NEWS_SLUG => 'WPSEO_News',
396 static::WOOCOMMERCE_SLUG => 'Yoast_WooCommerce_SEO',
397 static::VIDEO_SLUG => 'WPSEO_Video_Sitemap',
398 static::LOCAL_SLUG => 'WPSEO_Local_Core',
399 ];
400
401 if ( ! isset( $slug_to_class_map[ $slug ] ) ) {
402 return false;
403 }
404
405 return class_exists( $slug_to_class_map[ $slug ] );
406 }
407
408 /**
409 * Validates the addons and show a notice for the ones that are invalid.
410 */
411 public function validate_addons() {
412 $notification_center = Yoast_Notification_Center::get();
413
414 if ( $notification_center === null ) {
415 return;
416 }
417
418 $addons = [
419 'Yoast SEO Premium' => static::PREMIUM_SLUG,
420 'News SEO' => static::NEWS_SLUG,
421 'Yoast WooCommerce SEO' => static::WOOCOMMERCE_SLUG,
422 'Video SEO' => static::VIDEO_SLUG,
423 'Local SEO' => static::LOCAL_SLUG,
424 ];
425
426 foreach ( $addons as $product_name => $slug ) {
427 $notification = $this->create_notification( $product_name );
428
429 // Add a notification when the installed plugin isn't activated in My Yoast.
430 if ( $this->is_installed( $slug ) && ! $this->has_valid_subscription( $slug ) ) {
431 $notification_center->add_notification( $notification );
432
433 continue;
434 }
435
436 $notification_center->remove_notification( $notification );
437 }
438 }
439
440 /**
441 * Removes the site information transients.
442 *
443 * @codeCoverageIgnore
444 *
445 * @return void
446 */
447 public function remove_site_information_transients() {
448 delete_transient( self::SITE_INFORMATION_TRANSIENT );
449 delete_transient( self::SITE_INFORMATION_TRANSIENT_QUICK );
450 }
451
452 /**
453 * Creates an instance of Yoast_Notification.
454 *
455 * @param string $product_name The product to create the notification for.
456 *
457 * @return Yoast_Notification The created notification.
458 */
459 protected function create_notification( $product_name ) {
460 $notification_options = [
461 'type' => Yoast_Notification::ERROR,
462 'id' => 'wpseo-dismiss-' . sanitize_title_with_dashes( $product_name, null, 'save' ),
463 'capabilities' => 'wpseo_manage_options',
464 ];
465
466 return new Yoast_Notification(
467 sprintf(
468 /* translators: %1$s expands to the product name. %2$s expands to a link to My Yoast */
469 __( 'You are not receiving updates or support! Fix this problem by adding this site and enabling %1$s for it in %2$s.', 'wordpress-seo' ),
470 $product_name,
471 '<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/13j' ) . '" target="_blank">My Yoast</a>'
472 ),
473 $notification_options
474 );
475 }
476
477 /**
478 * Checks whether a plugin expiry date has been passed.
479 *
480 * @param stdClass $subscription Plugin subscription.
481 *
482 * @return bool Has the plugin expired.
483 */
484 protected function has_subscription_expired( $subscription ) {
485 return ( strtotime( $subscription->expiry_date ) - time() ) < 0;
486 }
487
488 /**
489 * Converts a subscription to plugin based format.
490 *
491 * @param stdClass $subscription The subscription to convert.
492 * @param stdClass $yoast_free_data The Yoast Free's data.
493 * @param bool $plugin_info Whether we're in the plugin information modal.
494 * @param string $plugin_file The plugin filename.
495 *
496 * @return stdClass The converted subscription.
497 */
498 protected function convert_subscription_to_plugin( $subscription, $yoast_free_data = null, $plugin_info = false, $plugin_file = '' ) {
499 // We need to replace h2's and h3's with h4's because the styling expects that.
500 $changelog = str_replace( '</h2', '</h4', str_replace( '<h2', '<h4', $subscription->product->changelog ) );
501 $changelog = str_replace( '</h3', '</h4', str_replace( '<h3', '<h4', $changelog ) );
502
503 // 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.
504 $defaults = [
505 // It can be expanded if we have the 'tested' and 'requires_php' data be returned from wp.org in the future.
506 'requires' => ( $plugin_info ) ? YOAST_SEO_WP_REQUIRED : null,
507 ];
508
509 return (object) [
510 'new_version' => $subscription->product->version,
511 'name' => $subscription->product->name,
512 'slug' => $subscription->product->slug,
513 'plugin' => $plugin_file,
514 'url' => $subscription->product->store_url,
515 'last_update' => $subscription->product->last_updated,
516 'homepage' => $subscription->product->store_url,
517 'download_link' => $subscription->product->download,
518 'package' => $subscription->product->download,
519 'sections' => [
520 'changelog' => $changelog,
521 'support' => $this->get_support_section(),
522 ],
523 'icons' => [
524 '2x' => $this->get_icon( $subscription->product->slug ),
525 ],
526 'update_supported' => true,
527 'banners' => $this->get_banners( $subscription->product->slug ),
528 // If we have extracted Yoast Free's data before, use that. If not, resort to the defaults.
529 'tested' => YOAST_SEO_WP_TESTED,
530 'requires' => isset( $yoast_free_data->requires ) ? $yoast_free_data->requires : $defaults['requires'],
531 'requires_php' => YOAST_SEO_PHP_REQUIRED,
532 ];
533 }
534
535 /**
536 * Returns the plugin's icon URL.
537 *
538 * @param string $slug The plugin slug.
539 *
540 * @return string The icon URL for this plugin.
541 */
542 protected function get_icon( $slug ) {
543 switch ( $slug ) {
544 case self::LOCAL_SLUG:
545 return 'https://yoa.st/local-seo-icon';
546 case self::NEWS_SLUG:
547 return 'https://yoa.st/news-seo-icon';
548 case self::PREMIUM_SLUG:
549 return 'https://yoa.st/yoast-seo-icon';
550 case self::VIDEO_SLUG:
551 return 'https://yoa.st/video-seo-icon';
552 case self::WOOCOMMERCE_SLUG:
553 return 'https://yoa.st/woo-seo-icon';
554 }
555 }
556
557 /**
558 * Return an array of plugin banner URLs.
559 *
560 * @param string $slug The plugin slug.
561 *
562 * @return string[]
563 */
564 protected function get_banners( $slug ) {
565 switch ( $slug ) {
566 case self::LOCAL_SLUG:
567 return [
568 'high' => 'https://yoa.st/yoast-seo-banner-local',
569 'low' => 'https://yoa.st/yoast-seo-banner-low-local',
570 ];
571 case self::NEWS_SLUG:
572 return [
573 'high' => 'https://yoa.st/yoast-seo-banner-news',
574 'low' => 'https://yoa.st/yoast-seo-banner-low-news',
575 ];
576 case self::PREMIUM_SLUG:
577 return [
578 'high' => 'https://yoa.st/yoast-seo-banner-premium',
579 'low' => 'https://yoa.st/yoast-seo-banner-low-premium',
580 ];
581 case self::VIDEO_SLUG:
582 return [
583 'high' => 'https://yoa.st/yoast-seo-banner-video',
584 'low' => 'https://yoa.st/yoast-seo-banner-low-video',
585 ];
586 case self::WOOCOMMERCE_SLUG:
587 return [
588 'high' => 'https://yoa.st/yoast-seo-banner-woo',
589 'low' => 'https://yoa.st/yoast-seo-banner-low-woo',
590 ];
591 }
592 }
593
594 /**
595 * Checks if the given plugin_file belongs to a Yoast addon.
596 *
597 * @param string $plugin_file Path to the plugin.
598 *
599 * @return bool True when plugin file is for a Yoast addon.
600 */
601 protected function is_yoast_addon( $plugin_file ) {
602 return $this->get_slug_by_plugin_file( $plugin_file ) !== '';
603 }
604
605 /**
606 * Retrieves the addon slug by given plugin file path.
607 *
608 * @param string $plugin_file The file path to the plugin.
609 *
610 * @return string The slug when found or empty string when not.
611 */
612 protected function get_slug_by_plugin_file( $plugin_file ) {
613 $addons = self::$addons;
614
615 // Yoast SEO Free isn't an addon, but we needed it in Premium to fetch translations.
616 if ( YoastSEO()->helpers->product->is_premium() ) {
617 $addons['wp-seo.php'] = self::FREE_SLUG;
618 }
619
620 foreach ( $addons as $addon => $addon_slug ) {
621 if ( strpos( $plugin_file, $addon ) !== false ) {
622 return $addon_slug;
623 }
624 }
625
626 return '';
627 }
628
629 /**
630 * Retrieves the installed Yoast addons.
631 *
632 * @return array The installed plugins.
633 */
634 protected function get_installed_addons() {
635 return array_filter( $this->get_plugins(), [ $this, 'is_yoast_addon' ], ARRAY_FILTER_USE_KEY );
636 }
637
638 /**
639 * Retrieves a list of active addons.
640 *
641 * @return array The active addons.
642 */
643 protected function get_active_addons() {
644 return array_filter( $this->get_installed_addons(), [ $this, 'is_plugin_active' ], ARRAY_FILTER_USE_KEY );
645 }
646
647 /**
648 * Retrieves the current sites from the API.
649 *
650 * @codeCoverageIgnore
651 *
652 * @return bool|stdClass Object when request is successful. False if not.
653 */
654 protected function request_current_sites() {
655 $api_request = new WPSEO_MyYoast_Api_Request( 'sites/current' );
656 if ( $api_request->fire() ) {
657 return $api_request->get_response();
658 }
659
660 return $this->get_site_information_default();
661 }
662
663 /**
664 * Retrieves the transient value with the site information.
665 *
666 * @codeCoverageIgnore
667 *
668 * @return stdClass|false The transient value.
669 */
670 protected function get_site_information_transient() {
671 global $pagenow;
672
673 // Force re-check on license & dashboard pages.
674 $current_page = $this->get_current_page();
675
676 // Check whether the licenses are valid or whether we need to show notifications.
677 $quick = ( $current_page === 'wpseo_licenses' || $current_page === 'wpseo_dashboard' );
678
679 // Also do a fresh request on Plugins & Core Update pages.
680 $quick = $quick || $pagenow === 'plugins.php';
681 $quick = $quick || $pagenow === 'update-core.php';
682
683 if ( $quick ) {
684 return get_transient( self::SITE_INFORMATION_TRANSIENT_QUICK );
685 }
686
687 return get_transient( self::SITE_INFORMATION_TRANSIENT );
688 }
689
690 /**
691 * Returns the current page.
692 *
693 * @codeCoverageIgnore
694 *
695 * @return string The current page.
696 */
697 protected function get_current_page() {
698 return filter_input( INPUT_GET, 'page' );
699 }
700
701 /**
702 * Sets the site information transient.
703 *
704 * @codeCoverageIgnore
705 *
706 * @param stdClass $site_information The site information to save.
707 *
708 * @return void
709 */
710 protected function set_site_information_transient( $site_information ) {
711 set_transient( self::SITE_INFORMATION_TRANSIENT, $site_information, DAY_IN_SECONDS );
712 set_transient( self::SITE_INFORMATION_TRANSIENT_QUICK, $site_information, 60 );
713 }
714
715 /**
716 * Retrieves all installed WordPress plugins.
717 *
718 * @codeCoverageIgnore
719 *
720 * @return array The plugins.
721 */
722 protected function get_plugins() {
723 if ( ! function_exists( 'get_plugins' ) ) {
724 require_once ABSPATH . 'wp-admin/includes/plugin.php';
725 }
726
727 return get_plugins();
728 }
729
730 /**
731 * Checks if the given plugin file belongs to an active plugin.
732 *
733 * @codeCoverageIgnore
734 *
735 * @param string $plugin_file The file path to the plugin.
736 *
737 * @return bool True when plugin is active.
738 */
739 protected function is_plugin_active( $plugin_file ) {
740 return is_plugin_active( $plugin_file );
741 }
742
743 /**
744 * Returns an object with no subscriptions.
745 *
746 * @codeCoverageIgnore
747 *
748 * @return stdClass Site information.
749 */
750 protected function get_site_information_default() {
751 return (object) [
752 'url' => WPSEO_Utils::get_home_url(),
753 'subscriptions' => [],
754 ];
755 }
756
757 /**
758 * Maps the plugin API response.
759 *
760 * @param object $site_information Site information as received from the API.
761 *
762 * @return stdClass Mapped site information.
763 */
764 protected function map_site_information( $site_information ) {
765 return (object) [
766 'url' => $site_information->url,
767 'subscriptions' => array_map( [ $this, 'map_subscription' ], $site_information->subscriptions ),
768 ];
769 }
770
771 /**
772 * Maps a plugin subscription.
773 *
774 * @param object $subscription Subscription information as received from the API.
775 *
776 * @return stdClass Mapped subscription.
777 */
778 protected function map_subscription( $subscription ) {
779 // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Not our properties.
780 return (object) [
781 'renewal_url' => $subscription->renewalUrl,
782 'expiry_date' => $subscription->expiryDate,
783 'product' => (object) [
784 'version' => $subscription->product->version,
785 'name' => $subscription->product->name,
786 'slug' => $subscription->product->slug,
787 'last_updated' => $subscription->product->lastUpdated,
788 'store_url' => $subscription->product->storeUrl,
789 // Ternary operator is necessary because download can be undefined.
790 'download' => isset( $subscription->product->download ) ? $subscription->product->download : null,
791 'changelog' => $subscription->product->changelog,
792 ],
793 ];
794 // phpcs:enable
795 }
796
797 /**
798 * Retrieves the site information.
799 *
800 * @return stdClass The site information.
801 */
802 private function get_site_information() {
803 if ( ! $this->has_installed_addons() ) {
804 return $this->get_site_information_default();
805 }
806
807 return $this->get_myyoast_site_information();
808 }
809
810 /**
811 * Retrieves the contents for the support section.
812 *
813 * @return string The support section content.
814 */
815 protected function get_support_section() {
816 return '<h4>' . __( 'Need support?', 'wordpress-seo' ) . '</h4>'
817 . '<p>'
818 /* translators: 1: expands to <a> that refers to the help page, 2: </a> closing tag. */
819 . 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>' )
820 . ' '
821 /* translators: %s expands to a mailto support link. */
822 . 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>' )
823 . '</p>';
824 }
825 }
826