[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-content/plugins/akismet/ -> class-akismet-compatible-plugins.php (source)

   1  <?php
   2  /**
   3   * Handles compatibility checks for Akismet with other plugins.
   4   *
   5   * @package Akismet
   6   * @since 5.4.0
   7   */
   8  
   9  declare( strict_types = 1 );
  10  
  11  // Following existing Akismet convention for file naming.
  12  // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
  13  
  14  /**
  15   * Class for managing compatibility checks for Akismet with other plugins.
  16   *
  17   * This class includes methods for determining whether specific plugins are
  18   * installed and active relative to the ability to work with Akismet.
  19   */
  20  class Akismet_Compatible_Plugins {
  21      /**
  22       * The endpoint for the compatible plugins API.
  23       *
  24       * @var string
  25       */
  26      protected const COMPATIBLE_PLUGIN_ENDPOINT = 'https://rest.akismet.com/1.2/compatible-plugins';
  27  
  28      /**
  29       * The error key for the compatible plugins API error.
  30       *
  31       * @var string
  32       */
  33      protected const COMPATIBLE_PLUGIN_API_ERROR = 'akismet_compatible_plugins_api_error';
  34  
  35      /**
  36       * The valid fields for a compatible plugin object.
  37       *
  38       * @var array
  39       */
  40      protected const COMPATIBLE_PLUGIN_FIELDS = array(
  41          'slug',
  42          'name',
  43          'logo',
  44          'help_url',
  45          'path',
  46      );
  47  
  48      /**
  49       * The cache key for the compatible plugins.
  50       *
  51       * @var string
  52       */
  53      protected const CACHE_KEY = 'akismet_compatible_plugin_list';
  54  
  55  
  56      /**
  57       * How many plugins should be visible by default?
  58       *
  59       * @var int
  60       */
  61      public const DEFAULT_VISIBLE_PLUGIN_COUNT = 2;
  62  
  63      /**
  64       * Get the list of active, installed compatible plugins.
  65       *
  66       * @param bool $bypass_cache Whether to bypass the cache and fetch fresh data.
  67       * @return WP_Error|array {
  68       *     Array of active, installed compatible plugins with their metadata.
  69       *     @type string $name     The display name of the plugin
  70       *     @type string $help_url URL to the plugin's help documentation
  71       *     @type string $logo     URL or path to the plugin's logo
  72       * }
  73       */
  74  	public static function get_installed_compatible_plugins( bool $bypass_cache = false ) {
  75          // Retrieve and validate the full compatible plugins list.
  76          $compatible_plugins = static::get_compatible_plugins( $bypass_cache );
  77  
  78          if ( empty( $compatible_plugins ) ) {
  79              return new WP_Error(
  80                  self::COMPATIBLE_PLUGIN_API_ERROR,
  81                  __( 'Error getting compatible plugins.', 'akismet' )
  82              );
  83          }
  84  
  85          // Retrieve all installed plugins once.
  86          $all_plugins = get_plugins();
  87  
  88          // Build list of compatible plugins that are both installed and active.
  89          $active_compatible_plugins = array();
  90  
  91          foreach ( $compatible_plugins as $slug => $data ) {
  92              $path = $data['path'];
  93              // Skip if not installed.
  94              if ( ! isset( $all_plugins[ $path ] ) ) {
  95                  continue;
  96              }
  97              // Check activation: per-site or network-wide (multisite).
  98              $site_active    = is_plugin_active( $path );
  99              $network_active = is_multisite() && is_plugin_active_for_network( $path );
 100              if ( $site_active || $network_active ) {
 101                  $active_compatible_plugins[ $slug ] = $data;
 102              }
 103          }
 104  
 105          return $active_compatible_plugins;
 106      }
 107  
 108      /**
 109       * Initializes action hooks for the class.
 110       *
 111       * @return void
 112       */
 113  	public static function init(): void {
 114          add_action( 'activated_plugin', array( static::class, 'handle_plugin_change' ), true );
 115          add_action( 'deactivated_plugin', array( static::class, 'handle_plugin_change' ), true );
 116      }
 117  
 118      /**
 119       * Handles plugin activation and deactivation events.
 120       *
 121       * @param string $plugin The path to the main plugin file from plugins directory.
 122       * @return void
 123       */
 124  	public static function handle_plugin_change( string $plugin ): void {
 125          $cached_plugins = static::get_cached_plugins();
 126  
 127          /**
 128           * Terminate if nothing's cached.
 129           */
 130          if ( false === $cached_plugins ) {
 131              return;
 132          }
 133  
 134          $plugin_change_should_invalidate_cache = in_array( $plugin, array_column( $cached_plugins, 'path' ) );
 135  
 136          /**
 137           * Purge the cache if the plugin is activated or deactivated.
 138           */
 139          if ( $plugin_change_should_invalidate_cache ) {
 140              static::purge_cache();
 141          }
 142      }
 143  
 144      /**
 145       * Gets plugins that are compatible with Akismet from the Akismet API.
 146       *
 147       * @param bool $bypass_cache Whether to bypass the cache and fetch fresh data.
 148       * @return array
 149       */
 150  	private static function get_compatible_plugins( bool $bypass_cache = false ): array {
 151          // Return cached result if present (false => cache miss; empty array is valid).
 152          $cached_plugins = static::get_cached_plugins();
 153  
 154          if ( false !== $cached_plugins && ! $bypass_cache ) {
 155              return $cached_plugins;
 156          }
 157  
 158          $response = wp_remote_get(
 159              self::COMPATIBLE_PLUGIN_ENDPOINT
 160          );
 161  
 162          $sanitized = static::validate_compatible_plugin_response( $response );
 163  
 164          if ( false === $sanitized ) {
 165              return array();
 166          }
 167  
 168          /**
 169           * Sets local static associative array of plugin data keyed by plugin slug.
 170           */
 171          $compatible_plugins = array();
 172  
 173          foreach ( $sanitized as $plugin ) {
 174              $compatible_plugins[ $plugin['slug'] ] = $plugin;
 175          }
 176  
 177          static::set_cached_plugins( $compatible_plugins );
 178  
 179          return $compatible_plugins;
 180      }
 181  
 182      /**
 183       * Validates a response object from the Compatible Plugins API.
 184       *
 185       * @param array|WP_Error $response
 186       * @return array|false
 187       */
 188  	private static function validate_compatible_plugin_response( $response ) {
 189          /**
 190           * Terminates the function if the response is a WP_Error object.
 191           */
 192          if ( is_wp_error( $response ) ) {
 193              return false;
 194          }
 195  
 196          /**
 197           * The response returned is an array of header + body string data.
 198           * This pops off the body string for processing.
 199           */
 200          $response_body = wp_remote_retrieve_body( $response );
 201  
 202          if ( empty( $response_body ) ) {
 203              return false;
 204          }
 205  
 206          $plugins = json_decode( $response_body, true );
 207  
 208          if ( false === is_array( $plugins ) ) {
 209              return false;
 210          }
 211  
 212          foreach ( $plugins as $plugin ) {
 213              if ( ! is_array( $plugin ) ) {
 214                  /**
 215                   * Skips to the next iteration if for some reason the plugin is not an array.
 216                   */
 217                  continue;
 218              }
 219  
 220              // Ensure that the plugin config read in from the API has all the required fields.
 221              $plugin_key_count = count(
 222                  array_intersect_key( $plugin, array_flip( static::COMPATIBLE_PLUGIN_FIELDS ) )
 223              );
 224  
 225              $does_not_have_all_required_fields = ! (
 226                  $plugin_key_count === count( static::COMPATIBLE_PLUGIN_FIELDS )
 227              );
 228  
 229              if ( $does_not_have_all_required_fields ) {
 230                  return false;
 231              }
 232  
 233              if ( false === static::has_valid_plugin_path( $plugin['path'] ) ) {
 234                  return false;
 235              }
 236          }
 237  
 238          return static::sanitize_compatible_plugin_response( $plugins );
 239      }
 240  
 241      /**
 242       * Validates a plugin path format.
 243       *
 244       * The path should be in the format of 'plugin-name/plugin-name.php'.
 245       * Allows alphanumeric characters, dashes, underscores, and optional dots in folder names.
 246       *
 247       * @param string $path
 248       * @return bool
 249       */
 250  	private static function has_valid_plugin_path( string $path ): bool {
 251          return preg_match( '/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9_-]+\.php$/', $path ) === 1;
 252      }
 253  
 254      /**
 255       * Sanitizes a response object from the Compatible Plugins API.
 256       *
 257       * @param array $plugins
 258       * @return array
 259       */
 260  	private static function sanitize_compatible_plugin_response( array $plugins = array() ): array {
 261          foreach ( $plugins as $key => $plugin ) {
 262              $plugins[ $key ]             = array_map( 'sanitize_text_field', $plugin );
 263              $plugins[ $key ]['help_url'] = sanitize_url( $plugins[ $key ]['help_url'] );
 264              $plugins[ $key ]['logo']     = sanitize_url( $plugins[ $key ]['logo'] );
 265          }
 266  
 267          return $plugins;
 268      }
 269  
 270      /**
 271       * @param array $plugins
 272       * @return bool
 273       */
 274  	private static function set_cached_plugins( array $plugins ): bool {
 275          $_blog_id = (int) get_current_blog_id();
 276  
 277          return set_transient(
 278              static::CACHE_KEY . "_$_blog_id",
 279              $plugins,
 280              DAY_IN_SECONDS
 281          );
 282      }
 283  
 284      /**
 285       * Attempts to get cached compatible plugins.
 286       *
 287       * @return mixed|false
 288       */
 289  	private static function get_cached_plugins() {
 290          $_blog_id = (int) get_current_blog_id();
 291  
 292          return get_transient(
 293              static::CACHE_KEY . "_$_blog_id"
 294          );
 295      }
 296  
 297      /**
 298       * Purges the cache for the compatible plugins.
 299       *
 300       * @return bool
 301       */
 302  	private static function purge_cache(): bool {
 303          $_blog_id = (int) get_current_blog_id();
 304  
 305          return delete_transient(
 306              static::CACHE_KEY . "_$_blog_id"
 307          );
 308      }
 309  }


Generated : Sat Apr 18 08:20:10 2026 Cross-referenced by PHPXref