123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- <?php
- /**
- * WPSEO plugin file.
- *
- * @package WPSEO\Inc
- */
- /**
- * Represents the addon manager.
- */
- class WPSEO_Addon_Manager {
- /**
- * Holds the name of the transient.
- *
- * @var string
- */
- const SITE_INFORMATION_TRANSIENT = 'wpseo_site_information';
- /**
- * Holds the slug for YoastSEO free.
- *
- * @var string
- */
- const FREE_SLUG = 'yoast-seo-wordpress';
- /**
- * Holds the slug for YoastSEO Premium.
- *
- * @var string
- */
- const PREMIUM_SLUG = 'yoast-seo-wordpress-premium';
- /**
- * Holds the slug for Yoast News.
- *
- * @var string
- */
- const NEWS_SLUG = 'yoast-seo-news';
- /**
- * Holds the slug for Video.
- *
- * @var string
- */
- const VIDEO_SLUG = 'yoast-seo-video';
- /**
- * Holds the slug for WooCommerce.
- *
- * @var string
- */
- const WOOCOMMERCE_SLUG = 'yoast-seo-woocommerce';
- /**
- * Holds the slug for Local.
- *
- * @var string
- */
- const LOCAL_SLUG = 'yoast-seo-local';
- /**
- * The expected addon data.
- *
- * @var array
- */
- protected static $addons = [
- 'wp-seo-premium.php' => self::PREMIUM_SLUG,
- 'wpseo-news.php' => self::NEWS_SLUG,
- 'video-seo.php' => self::VIDEO_SLUG,
- 'wpseo-woocommerce.php' => self::WOOCOMMERCE_SLUG,
- 'local-seo.php' => self::LOCAL_SLUG,
- ];
- /**
- * Holds the site information data.
- *
- * @var object
- */
- private $site_information;
- /**
- * Hooks into WordPress.
- *
- * @codeCoverageIgnore
- *
- * @return void
- */
- public function register_hooks() {
- add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_updates' ] );
- add_filter( 'plugins_api', [ $this, 'get_plugin_information' ], 10, 3 );
- }
- /**
- * Gets the subscriptions for current site.
- *
- * @return stdClass The subscriptions.
- */
- public function get_subscriptions() {
- return $this->get_site_information()->subscriptions;
- }
- /**
- * Retrieves the subscription for the given slug.
- *
- * @param string $slug The plugin slug to retrieve.
- *
- * @return stdClass|false Subscription data when found, false when not found.
- */
- public function get_subscription( $slug ) {
- foreach ( $this->get_subscriptions() as $subscription ) {
- if ( $subscription->product->slug === $slug ) {
- return $subscription;
- }
- }
- return false;
- }
- /**
- * Retrieves a list of (subscription) slugs by the active addons.
- *
- * @return array The slugs.
- */
- public function get_subscriptions_for_active_addons() {
- $active_addons = array_keys( $this->get_active_addons() );
- $subscription_slugs = array_map( [ $this, 'get_slug_by_plugin_file' ], $active_addons );
- $subscriptions = [];
- foreach ( $subscription_slugs as $subscription_slug ) {
- $subscriptions[ $subscription_slug ] = $this->get_subscription( $subscription_slug );
- }
- return $subscriptions;
- }
- /**
- * Retrieves a list of versions for each addon.
- *
- * @return array The addon versions.
- */
- public function get_installed_addons_versions() {
- $addon_versions = [];
- foreach ( $this->get_installed_addons() as $plugin_file => $installed_addon ) {
- $addon_versions[ $this->get_slug_by_plugin_file( $plugin_file ) ] = $installed_addon['Version'];
- }
- return $addon_versions;
- }
- /**
- * Retrieves the plugin information from the subscriptions.
- *
- * @param stdClass|false $data The result object. Default false.
- * @param string $action The type of information being requested from the Plugin Installation API.
- * @param stdClass $args Plugin API arguments.
- *
- * @return object Extended plugin data.
- */
- public function get_plugin_information( $data, $action, $args ) {
- if ( $action !== 'plugin_information' ) {
- return $data;
- }
- if ( ! isset( $args->slug ) ) {
- return $data;
- }
- $subscription = $this->get_subscription( $args->slug );
- if ( ! $subscription || $this->has_subscription_expired( $subscription ) ) {
- return $data;
- }
- return $this->convert_subscription_to_plugin( $subscription );
- }
- /**
- * Checks if the subscription for the given slug is valid.
- *
- * @param string $slug The plugin slug to retrieve.
- *
- * @return bool True when the subscription is valid.
- */
- public function has_valid_subscription( $slug ) {
- $subscription = $this->get_subscription( $slug );
- // An non-existing subscription is never valid.
- if ( $subscription === false ) {
- return false;
- }
- return ! $this->has_subscription_expired( $subscription );
- }
- /**
- * Checks if there are addon updates.
- *
- * @param stdClass|mixed $data The current data for update_plugins.
- *
- * @return stdClass Extended data for update_plugins.
- */
- public function check_for_updates( $data ) {
- if ( empty( $data ) ) {
- return $data;
- }
- foreach ( $this->get_installed_addons() as $plugin_file => $installed_plugin ) {
- $subscription_slug = $this->get_slug_by_plugin_file( $plugin_file );
- $subscription = $this->get_subscription( $subscription_slug );
- if ( ! $subscription || $this->has_subscription_expired( $subscription ) ) {
- continue;
- }
- if ( version_compare( $installed_plugin['Version'], $subscription->product->version, '<' ) ) {
- $data->response[ $plugin_file ] = $this->convert_subscription_to_plugin( $subscription );
- }
- }
- return $data;
- }
- /**
- * Checks whether a plugin expiry date has been passed.
- *
- * @param stdClass $subscription Plugin subscription.
- *
- * @return bool Has the plugin expired.
- */
- protected function has_subscription_expired( $subscription ) {
- return ( strtotime( $subscription->expiry_date ) - time() ) < 0;
- }
- /**
- * Converts a subscription to plugin based format.
- *
- * @param stdClass $subscription The subscription to convert.
- *
- * @return stdClass The converted subscription.
- */
- protected function convert_subscription_to_plugin( $subscription ) {
- return (object) [
- 'new_version' => $subscription->product->version,
- 'name' => $subscription->product->name,
- 'slug' => $subscription->product->slug,
- 'url' => $subscription->product->store_url,
- 'last_update' => $subscription->product->last_updated,
- 'homepage' => $subscription->product->store_url,
- 'download_link' => $subscription->product->download,
- 'package' => $subscription->product->download,
- 'sections' =>
- [
- 'changelog' => $subscription->product->changelog,
- ],
- ];
- }
- /**
- * Checks if the given plugin_file belongs to a Yoast addon.
- *
- * @param string $plugin_file Path to the plugin.
- *
- * @return bool True when plugin file is for a Yoast addon.
- */
- protected function is_yoast_addon( $plugin_file ) {
- return $this->get_slug_by_plugin_file( $plugin_file ) !== '';
- }
- /**
- * Retrieves the addon slug by given plugin file path.
- *
- * @param string $plugin_file The file path to the plugin.
- *
- * @return string The slug when found or empty string when not.
- */
- protected function get_slug_by_plugin_file( $plugin_file ) {
- $addons = self::$addons;
- // Yoast SEO Free isn't an addon, but we needed it in Premium to fetch translations.
- if ( WPSEO_Utils::is_yoast_seo_premium() ) {
- $addons['wp-seo.php'] = self::FREE_SLUG;
- }
- foreach ( $addons as $addon => $addon_slug ) {
- if ( strpos( $plugin_file, $addon ) !== false ) {
- return $addon_slug;
- }
- }
- return '';
- }
- /**
- * Retrieves the installed Yoast addons.
- *
- * @return array The installed plugins.
- */
- protected function get_installed_addons() {
- return $this->filter_by_key( $this->get_plugins(), [ $this, 'is_yoast_addon' ] );
- }
- /**
- * Retrieves a list of active addons.
- *
- * @return array The active addons.
- */
- protected function get_active_addons() {
- return $this->filter_by_key( $this->get_installed_addons(), [ $this, 'is_plugin_active' ] );
- }
- /**
- * Retrieves the current sites from the API.
- *
- * @codeCoverageIgnore
- *
- * @return bool|stdClass Object when request is successful. False if not.
- */
- protected function request_current_sites() {
- $api_request = new WPSEO_MyYoast_Api_Request( 'sites/current' );
- if ( $api_request->fire() ) {
- return $api_request->get_response();
- }
- return $this->get_site_information_default();
- }
- /**
- * Retrieves the transient value with the site information.
- *
- * @codeCoverageIgnore
- *
- * @return stdClass|false The transient value.
- */
- protected function get_site_information_transient() {
- global $pagenow;
- // Force re-check on license & dashboard pages.
- $current_page = $this->get_current_page();
- // Check whether the licenses are valid or whether we need to show notifications.
- $exclude_cache = ( $current_page === 'wpseo_licenses' || $current_page === 'wpseo_dashboard' );
- // Also do a fresh request on Plugins & Core Update pages.
- $exclude_cache = $exclude_cache || $pagenow === 'plugins.php';
- $exclude_cache = $exclude_cache || $pagenow === 'update-core.php';
- if ( $exclude_cache ) {
- return false;
- }
- return get_transient( self::SITE_INFORMATION_TRANSIENT );
- }
- /**
- * Returns the current page.
- *
- * @codeCoverageIgnore
- *
- * @return string The current page.
- */
- protected function get_current_page() {
- return filter_input( INPUT_GET, 'page' );
- }
- /**
- * Sets the site information transient.
- *
- * @codeCoverageIgnore
- *
- * @param stdClass $site_information The site information to save.
- *
- * @return void
- */
- protected function set_site_information_transient( $site_information ) {
- set_transient( self::SITE_INFORMATION_TRANSIENT, $site_information, DAY_IN_SECONDS );
- }
- /**
- * Retrieves all installed WordPress plugins.
- *
- * @codeCoverageIgnore
- *
- * @return array The plugins.
- */
- protected function get_plugins() {
- return get_plugins();
- }
- /**
- * Checks if the given plugin file belongs to an active plugin.
- *
- * @codeCoverageIgnore
- *
- * @param string $plugin_file The file path to the plugin.
- *
- * @return bool True when plugin is active.
- */
- protected function is_plugin_active( $plugin_file ) {
- return is_plugin_active( $plugin_file );
- }
- /**
- * Returns an object with no subscriptions.
- *
- * @codeCoverageIgnore
- *
- * @return stdClass Site information.
- */
- protected function get_site_information_default() {
- return (object) [
- 'url' => WPSEO_Utils::get_home_url(),
- 'subscriptions' => [],
- ];
- }
- /**
- * Checks if there are any installed addons.
- *
- * @return bool True when there are installed Yoast addons.
- */
- protected function has_installed_addons() {
- $installed_addons = $this->get_installed_addons();
- return ! empty( $installed_addons );
- }
- /**
- * Filters the given array by its keys.
- *
- * This method is temporary. When WordPress has minimal PHP 5.6 support we can change this to:
- *
- * array_filter( $array_to_filter, $filter, ARRAY_FILTER_USE_KEY )
- *
- * @codeCoverageIgnore
- *
- * @param array $array_to_filter The array to filter.
- * @param callable $callback The filter callback.
- *
- * @return array The filtered array,
- */
- private function filter_by_key( $array_to_filter, $callback ) {
- $keys_to_filter = array_filter( array_keys( $array_to_filter ), $callback );
- $filtered_array = [];
- foreach ( $keys_to_filter as $filtered_key ) {
- $filtered_array[ $filtered_key ] = $array_to_filter[ $filtered_key ];
- }
- return $filtered_array;
- }
- /**
- * Maps the plugin API response.
- *
- * @param object $site_information Site information as received from the API.
- *
- * @return object Mapped site information.
- */
- protected function map_site_information( $site_information ) {
- return (object) [
- 'url' => $site_information->url,
- 'subscriptions' => array_map( [ $this, 'map_subscription' ], $site_information->subscriptions ),
- ];
- }
- /**
- * Maps a plugin subscription.
- *
- * @param object $subscription Subscription information as received from the API.
- *
- * @return object Mapped subscription.
- */
- protected function map_subscription( $subscription ) {
- // @codingStandardsIgnoreStart
- return (object) array(
- 'renewal_url' => $subscription->renewalUrl,
- 'expiry_date' => $subscription->expiryDate,
- 'product' => (object) array(
- 'version' => $subscription->product->version,
- 'name' => $subscription->product->name,
- 'slug' => $subscription->product->slug,
- 'last_updated' => $subscription->product->lastUpdated,
- 'store_url' => $subscription->product->storeUrl,
- // Ternary operator is necessary because download can be undefined.
- 'download' => isset( $subscription->product->download ) ? $subscription->product->download : null,
- 'changelog' => $subscription->product->changelog,
- ),
- );
- // @codingStandardsIgnoreStop
- }
- /**
- * Retrieves the site information.
- *
- * @return stdClass The site information.
- */
- private function get_site_information() {
- if ( ! $this->has_installed_addons() ) {
- return $this->get_site_information_default();
- }
- if ( $this->site_information === null ) {
- $this->site_information = $this->get_site_information_transient();
- }
- if ( $this->site_information ) {
- return $this->site_information;
- }
- $this->site_information = $this->request_current_sites();
- if ( $this->site_information ) {
- $this->site_information = $this->map_site_information( $this->site_information );
- $this->set_site_information_transient( $this->site_information );
- return $this->site_information;
- }
- return $this->get_site_information_default();
- }
- }
|