← All changes
|
vendor/codeinwp/themeisle-sdk/src/Modules/Featured_plugins.php
+102
-10
4.2.11
→
trunk
View file →
| @@ -149,8 +149,9 @@ | ||
| 149 | 149 | } |
| 150 | 150 | |
| 151 | 151 | if ( isset( $args->page ) && 1 === (int) $args->page && isset( $args->search ) && ! empty( $args->search ) ) { |
| 152 | 152 | $original_count = count( (array) $res->plugins ); |
| 153 | + $res->plugins = $this->maybe_prepend_easy_mcp_plugin( $res->plugins, $args ); | |
| 153 | 154 | $res->plugins = $this->maybe_prepend_lms_plugin( $res->plugins, $args ); |
| 154 | 155 | |
| 155 | 156 | return $this->adjust_results_count( $res, count( (array) $res->plugins ) - $original_count ); |
| 156 | 157 | } |
| @@ -181,19 +182,78 @@ | ||
| 181 | 182 | * @return array |
| 182 | 183 | */ |
| 183 | 184 | private function maybe_prepend_lms_plugin( $plugins, $args ) { |
| 184 | 185 | $search = isset( $args->search ) ? strtolower( $args->search ) : ''; |
| 185 | - if ( $this->matches_lms_search_keywords( $search ) ) { | |
| 186 | - $filter_slugs = apply_filters( 'themeisle_sdk_masteriyo_filter_slugs', [ 'learning-management-system' ] ); | |
| 187 | - $masteriyo = $this->get_plugins_filtered_from_author( $args, $filter_slugs, 'masteriyo' ); | |
| 186 | + if ( ! self::matches_lms_search_keywords( $search ) ) { | |
| 187 | + return $plugins; | |
| 188 | + } | |
| 188 | 189 | |
| 189 | - if ( ! empty( $masteriyo ) ) { | |
| 190 | - // Remove existing copies of the injected plugins to avoid duplicates. | |
| 191 | - $plugins = $this->remove_plugins_by_slug( (array) $plugins, $this->get_plugin_slugs( $masteriyo ) ); | |
| 192 | - $plugins = array_merge( $masteriyo, $plugins ); | |
| 190 | + $filter_slugs = apply_filters( 'themeisle_sdk_masteriyo_filter_slugs', [ 'learning-management-system' ] ); | |
| 191 | + | |
| 192 | + return $this->prepend_author_plugins( $plugins, $args, $filter_slugs, 'masteriyo' ); | |
| 193 | + } | |
| 194 | + | |
| 195 | + /** | |
| 196 | + * Prepend the given author's plugins to the list, removing existing copies to avoid duplicates. | |
| 197 | + * | |
| 198 | + * @param array $plugins The plugins array. | |
| 199 | + * @param object $args The plugin API arguments. | |
| 200 | + * @param array $filter_slugs The slugs to inject. | |
| 201 | + * @param string $author The wp.org author that owns the slugs. | |
| 202 | + * | |
| 203 | + * @return array | |
| 204 | + */ | |
| 205 | + private function prepend_author_plugins( $plugins, $args, $filter_slugs, $author ) { | |
| 206 | + $injected = $this->get_plugins_filtered_from_author( $args, $filter_slugs, $author ); | |
| 207 | + if ( empty( $injected ) ) { | |
| 208 | + return $plugins; | |
| 209 | + } | |
| 210 | + | |
| 211 | + $plugins = $this->remove_plugins_by_slug( (array) $plugins, $this->get_plugin_slugs( $injected ) ); | |
| 212 | + | |
| 213 | + return array_merge( $injected, $plugins ); | |
| 214 | + } | |
| 215 | + | |
| 216 | + /** | |
| 217 | + * Prepend the Easy MCP AI plugin if the search query matches AI/MCP-related terms. | |
| 218 | + * | |
| 219 | + * @param array $plugins The plugins array. | |
| 220 | + * @param object $args The plugin API arguments. | |
| 221 | + * @return array | |
| 222 | + */ | |
| 223 | + private function maybe_prepend_easy_mcp_plugin( $plugins, $args ) { | |
| 224 | + $search = isset( $args->search ) ? strtolower( $args->search ) : ''; | |
| 225 | + | |
| 226 | + // The LMS prepend keeps precedence — never stack two injected cards. | |
| 227 | + if ( self::matches_lms_search_keywords( $search ) || ! self::matches_ai_search_keywords( $search ) ) { | |
| 228 | + return $plugins; | |
| 229 | + } | |
| 230 | + | |
| 231 | + $filter_slugs = apply_filters( 'themeisle_sdk_easy_mcp_filter_slugs', [ 'easy-mcp-ai' ] ); | |
| 232 | + | |
| 233 | + return $this->prepend_author_plugins( $plugins, $args, $filter_slugs, 'easymcpai' ); | |
| 234 | + } | |
| 235 | + | |
| 236 | + /** | |
| 237 | + * Check if a plugin search query contains AI/MCP terms or supported client brands. | |
| 238 | + * | |
| 239 | + * Brand names of unrelated AI vendors are deliberately not matched — only the | |
| 240 | + * category terms and the clients Easy MCP AI itself integrates with. | |
| 241 | + * | |
| 242 | + * @param string $search Search query. | |
| 243 | + * | |
| 244 | + * @return bool True if the search query matches AI/MCP-related terms. | |
| 245 | + */ | |
| 246 | + public static function matches_ai_search_keywords( $search ) { | |
| 247 | + // Distinctive names match as substrings (for example, "claudecode"). | |
| 248 | + foreach ( array( 'model context protocol', 'claude', 'chatgpt' ) as $keyword ) { | |
| 249 | + if ( false !== stripos( $search, $keyword ) ) { | |
| 250 | + return true; | |
| 193 | 251 | } |
| 194 | 252 | } |
| 195 | - return $plugins; | |
| 253 | + | |
| 254 | + // Short terms need boundaries (for example, not "email" or "fulfillment"). | |
| 255 | + return preg_match( '/(^|[^a-z0-9])(ai|mcp|llms?)([^a-z0-9]|$)/i', $search ) === 1; | |
| 196 | 256 | } |
| 197 | 257 | |
| 198 | 258 | /** |
| 199 | 259 | * Extract the slugs from a list of plugin entries. |
| @@ -257,9 +317,9 @@ | ||
| 257 | 317 | * @param string $search Search query. |
| 258 | 318 | * |
| 259 | 319 | * @return bool True if the search query matches LMS-related terms. |
| 260 | 320 | */ |
| 261 | - private function matches_lms_search_keywords( $search ) { | |
| 321 | + public static function matches_lms_search_keywords( $search ) { | |
| 262 | 322 | $lms_keywords = array( |
| 263 | 323 | 'lms', |
| 264 | 324 | 'learn', |
| 265 | 325 | 'course', |
| @@ -298,9 +358,41 @@ | ||
| 298 | 358 | $themeisle_filter_slugs = apply_filters( 'themeisle_sdk_themeisle_filter_slugs', [ 'otter-blocks', 'wp-cloudflare-page-cache' ] ); |
| 299 | 359 | $filtered_from_themeisle = $this->get_plugins_filtered_from_author( $args, $themeisle_filter_slugs ); |
| 300 | 360 | $featured = array_merge( $featured, $filtered_from_themeisle ); |
| 301 | 361 | |
| 302 | - return $featured; | |
| 362 | + // Easy MCP AI targets the Abilities API, available since WordPress 6.9. | |
| 363 | + if ( version_compare( get_bloginfo( 'version' ), '6.9', '>=' ) ) { | |
| 364 | + $easy_mcp_filter_slugs = apply_filters( 'themeisle_sdk_easy_mcp_filter_slugs', [ 'easy-mcp-ai' ] ); | |
| 365 | + $filtered_from_easy_mcp = $this->get_plugins_filtered_from_author( $args, $easy_mcp_filter_slugs, 'easymcpai' ); | |
| 366 | + $featured = array_merge( $featured, $filtered_from_easy_mcp ); | |
| 367 | + } | |
| 368 | + | |
| 369 | + return $this->dedupe_plugins_by_slug( $featured ); | |
| 370 | + } | |
| 371 | + | |
| 372 | + /** | |
| 373 | + * Remove entries with a duplicate slug from a list of plugins, keeping the first occurrence. | |
| 374 | + * | |
| 375 | + * @param array $plugins The plugins list. | |
| 376 | + * | |
| 377 | + * @return array | |
| 378 | + */ | |
| 379 | + private function dedupe_plugins_by_slug( $plugins ) { | |
| 380 | + $seen = []; | |
| 381 | + $deduped = []; | |
| 382 | + | |
| 383 | + foreach ( $plugins as $plugin ) { | |
| 384 | + $array_plugin = (array) $plugin; | |
| 385 | + if ( isset( $array_plugin['slug'] ) ) { | |
| 386 | + if ( isset( $seen[ $array_plugin['slug'] ] ) ) { | |
| 387 | + continue; | |
| 388 | + } | |
| 389 | + $seen[ $array_plugin['slug'] ] = true; | |
| 390 | + } | |
| 391 | + $deduped[] = $plugin; | |
| 392 | + } | |
| 393 | + | |
| 394 | + return $deduped; | |
| 303 | 395 | } |
| 304 | 396 | |
| 305 | 397 | /** |
| 306 | 398 | * Get plugins filtered from an author. |