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

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