| 1 |
<?php |
| 2 |
/** |
| 3 |
* Product context detector. |
| 4 |
* |
| 5 |
* @package zip-ai |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace ZipAI\MCP\Classes\Core; |
| 9 |
|
| 10 |
// Exit if accessed directly. |
| 11 |
if ( ! defined( 'ABSPATH' ) ) { |
| 12 |
exit; |
| 13 |
} |
| 14 |
|
| 15 |
/** |
| 16 |
* Maps the current WP admin screen to a product slug (Sure-family, Astra, |
| 17 |
* Elementor, WooCommerce). Drives the chat assistant's context-aware welcome |
| 18 |
* suggestions: the React side does a dictionary lookup on this slug, with the |
| 19 |
* suggestion lists in `src/constants/emptyThreadSuggestions.js`. |
| 20 |
* |
| 21 |
* Detection is server-side because `get_current_screen()` is the authoritative |
| 22 |
* source for screen identity. Only products that register WP Abilities are |
| 23 |
* mapped — UAE registers none, so it is intentionally omitted. |
| 24 |
*/ |
| 25 |
class Product_Context { |
| 26 |
|
| 27 |
/** |
| 28 |
* Exact `get_current_screen()->id` => product slug. Used for short, generic |
| 29 |
* ids (core WP screens + single-word slugs) that would over-match if checked |
| 30 |
* as substrings — a third-party submenu page carries the parent menu slug in |
| 31 |
* its id/parent_base (e.g. a page under Users has `parent_base = users`), so |
| 32 |
* substring matching `users` there would mistag it. Exact id match avoids it. |
| 33 |
* |
| 34 |
* Core screens map to what the assistant can actually do (plugin/theme/user/ |
| 35 |
* comment abilities + the WP-CLI runner, image generation, page builder). |
| 36 |
* |
| 37 |
* @var array<string,string> |
| 38 |
*/ |
| 39 |
private const ID_MAP = array( |
| 40 |
'plugins' => 'wp-plugins', |
| 41 |
'plugin-install' => 'wp-plugins', |
| 42 |
'themes' => 'wp-themes', |
| 43 |
'theme-install' => 'wp-themes', |
| 44 |
'edit-comments' => 'wp-comments', |
| 45 |
'upload' => 'wp-media', |
| 46 |
'users' => 'wp-users', |
| 47 |
'user-new' => 'wp-users', |
| 48 |
'edit-product' => 'woocommerce', |
| 49 |
'toplevel_page_portal' => 'suredash', |
| 50 |
// Posts/Pages list screens intentionally not mapped — the chat falls |
| 51 |
// through to the default suggestions there. |
| 52 |
); |
| 53 |
|
| 54 |
/** |
| 55 |
* Distinctive brand token => product slug. Matched as a substring of the |
| 56 |
* screen haystack ("id parent_base post_type") so product submenus inherit |
| 57 |
* the parent product. Safe to substring-match because these are unique |
| 58 |
* vendor slugs, not common English words. First hit wins. |
| 59 |
* |
| 60 |
* @var array<string,string> |
| 61 |
*/ |
| 62 |
private const TOKEN_MAP = array( |
| 63 |
'zip-ai-snippets' => 'snippets', |
| 64 |
'woocommerce' => 'woocommerce', |
| 65 |
'sureforms' => 'sureforms', |
| 66 |
'suremail' => 'suremail', |
| 67 |
'surerank' => 'surerank', |
| 68 |
'surecookie' => 'surecookie', |
| 69 |
'surecontact' => 'surecontact', |
| 70 |
'surecart' => 'surecart', |
| 71 |
'sc-dashboard' => 'surecart', |
| 72 |
'cartflows' => 'cartflows', |
| 73 |
'spectra' => 'spectra', |
| 74 |
'latepoint' => 'latepoint', |
| 75 |
'sigmize' => 'sigmize', |
| 76 |
'elementor' => 'elementor', |
| 77 |
'astra' => 'astra', |
| 78 |
); |
| 79 |
|
| 80 |
/** |
| 81 |
* Detect the active product for the current admin screen. |
| 82 |
* |
| 83 |
* @since 1.0.0 |
| 84 |
* @return string|null Product slug, or null off any known product screen. |
| 85 |
*/ |
| 86 |
public static function detect() { |
| 87 |
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) { |
| 88 |
return null; |
| 89 |
} |
| 90 |
|
| 91 |
$screen = get_current_screen(); |
| 92 |
if ( ! $screen ) { |
| 93 |
return null; |
| 94 |
} |
| 95 |
|
| 96 |
// Core WordPress Settings screens (Settings > General/Writing/Reading/ |
| 97 |
// Discussion/Media/Permalinks) all have a screen id prefixed `options-`. |
| 98 |
// Match the id prefix ONLY — not parent_base — so plugin pages that |
| 99 |
// merely sit under the Settings menu (id `settings_page_*`, including our |
| 100 |
// own assistant/snippets pages) are not mistagged as core settings. |
| 101 |
if ( 0 === strpos( (string) $screen->id, 'options-' ) ) { |
| 102 |
return 'wp-settings'; |
| 103 |
} |
| 104 |
|
| 105 |
// Exact id match for generic/core screens (no parent_base over-match). |
| 106 |
if ( isset( self::ID_MAP[ $screen->id ] ) ) { |
| 107 |
return self::ID_MAP[ $screen->id ]; |
| 108 |
} |
| 109 |
|
| 110 |
// Distinctive brand tokens — substring across the screen haystack. |
| 111 |
$haystack = strtolower( |
| 112 |
$screen->id . ' ' . ( $screen->parent_base ?? '' ) . ' ' . $screen->post_type |
| 113 |
); |
| 114 |
|
| 115 |
foreach ( self::TOKEN_MAP as $token => $product ) { |
| 116 |
if ( false !== strpos( $haystack, $token ) ) { |
| 117 |
return $product; |
| 118 |
} |
| 119 |
} |
| 120 |
|
| 121 |
return null; |
| 122 |
} |
| 123 |
} |
| 124 |
|