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


Generated : Wed May 14 08:20:01 2025 Cross-referenced by PHPXref