| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Apr 18 08:20:10 2026 | Cross-referenced by PHPXref |