[ 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 * 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Wed May 14 08:20:01 2025 | Cross-referenced by PHPXref |