| 1 |
<?php |
| 2 |
defined('ABSPATH') or die('Unauthorized Access'); |
| 3 |
|
| 4 |
// WP 7.0 AI Client integration. Adds an "Explain with AI" capability that |
| 5 |
// turns a php.ini directive name, a failing config check, or a security |
| 6 |
// header into a short plain-English explanation. Credentials are managed |
| 7 |
// entirely by the core Connectors API — we never touch API keys. |
| 8 |
// |
| 9 |
// Falls back silently on cores without wp_ai_client_prompt() so the rest |
| 10 |
// of the plugin keeps working on WP < 7.0. |
| 11 |
|
| 12 |
class Phpinfo_WP_AI_Explain { |
| 13 |
|
| 14 |
const NONCE = 'phpinfowp_ai_explain'; |
| 15 |
const ACTION = 'phpinfowp_ai_explain'; |
| 16 |
|
| 17 |
public static function register(): void { |
| 18 |
if (!self::available()) return; |
| 19 |
add_action('wp_ajax_' . self::ACTION, [self::class, 'ajax_explain']); |
| 20 |
} |
| 21 |
|
| 22 |
public static function available(): bool { |
| 23 |
return function_exists('wp_ai_client_prompt'); |
| 24 |
} |
| 25 |
|
| 26 |
// Localized data for the JS handler. Returns an empty array when the |
| 27 |
// AI Client isn't available so the JS can hide the button. |
| 28 |
public static function js_config(): array { |
| 29 |
return [ |
| 30 |
'available' => self::available(), |
| 31 |
'action' => self::ACTION, |
| 32 |
'nonce' => wp_create_nonce(self::NONCE), |
| 33 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 34 |
'i18n' => [ |
| 35 |
'explain' => __('Explain with AI', 'phpinfo-wp'), |
| 36 |
'thinking' => __('Asking AI…', 'phpinfo-wp'), |
| 37 |
'error' => __('AI request failed.', 'phpinfo-wp'), |
| 38 |
], |
| 39 |
]; |
| 40 |
} |
| 41 |
|
| 42 |
public static function ajax_explain(): void { |
| 43 |
check_ajax_referer(self::NONCE, 'nonce'); |
| 44 |
if (!current_user_can('manage_options')) { |
| 45 |
wp_send_json_error(['message' => __('Forbidden.', 'phpinfo-wp')], 403); |
| 46 |
} |
| 47 |
if (!self::available()) { |
| 48 |
wp_send_json_error(['message' => __('AI Client is not available. Requires WordPress 7.0+ with a configured AI connector.', 'phpinfo-wp')], 501); |
| 49 |
} |
| 50 |
|
| 51 |
$topic = sanitize_key($_POST['topic'] ?? ''); |
| 52 |
$context = sanitize_text_field(wp_unslash($_POST['context'] ?? '')); |
| 53 |
$value = sanitize_text_field(wp_unslash($_POST['value'] ?? '')); |
| 54 |
|
| 55 |
$prompt = self::build_prompt($topic, $context, $value); |
| 56 |
if ($prompt === '') { |
| 57 |
wp_send_json_error(['message' => __('Unknown topic.', 'phpinfo-wp')], 400); |
| 58 |
} |
| 59 |
|
| 60 |
$result = wp_ai_client_prompt($prompt)->generate_text(); |
| 61 |
if (is_wp_error($result)) { |
| 62 |
wp_send_json_error(['message' => $result->get_error_message()], 502); |
| 63 |
} |
| 64 |
wp_send_json_success(['text' => (string) $result]); |
| 65 |
} |
| 66 |
|
| 67 |
// Prompts are intentionally short, factual, and bounded — long answers |
| 68 |
// waste the user's connector budget and clutter the inline UI. |
| 69 |
private static function build_prompt(string $topic, string $context, string $value): string { |
| 70 |
switch ($topic) { |
| 71 |
case 'directive': |
| 72 |
return sprintf( |
| 73 |
'Explain the PHP directive `%s` in the context of a WordPress site. Cover: what it does, recommended value, and the typical symptom when it is misconfigured. Use 3–4 short sentences. Plain text, no markdown.', |
| 74 |
$context |
| 75 |
); |
| 76 |
case 'config_issue': |
| 77 |
return sprintf( |
| 78 |
'A WordPress site has the PHP directive `%s` currently set to `%s`. Explain in 3–4 short sentences why this value is below the recommended setting, the user-visible symptom (e.g. uploads failing, builder breaking), and the safest way to fix it on a shared host. Plain text, no markdown.', |
| 79 |
$context, $value !== '' ? $value : '(not set)' |
| 80 |
); |
| 81 |
case 'header': |
| 82 |
return sprintf( |
| 83 |
'Explain the HTTP security header `%s` for a WordPress site. Cover: what it protects against, a sensible recommended value, and the realistic risk of leaving it off. Use 3–4 short sentences. Plain text, no markdown.', |
| 84 |
$context |
| 85 |
); |
| 86 |
case 'extension': |
| 87 |
return sprintf( |
| 88 |
'Explain the PHP extension `%s` in the context of a WordPress site. Cover: what it provides, common plugins that need it, and the symptom when it is missing. Use 3–4 short sentences. Plain text, no markdown.', |
| 89 |
$context |
| 90 |
); |
| 91 |
case 'update_break': |
| 92 |
// $context = API name (e.g. ".live()" or "get_currentuserinfo()"), |
| 93 |
// $value = target WordPress version being audited. |
| 94 |
return sprintf( |
| 95 |
'A WordPress plugin or theme calls `%s`, which is flagged when updating to WordPress %s. Explain in 3–4 short sentences: what this API was, why it is deprecated or removed in modern WordPress, the realistic symptom on the site after the update (e.g. JavaScript stops working, PHP notice in the log), and the modern replacement. Plain text, no markdown.', |
| 96 |
$context, $value !== '' ? $value : 'a newer version' |
| 97 |
); |
| 98 |
default: |
| 99 |
return ''; |
| 100 |
} |
| 101 |
} |
| 102 |
} |
| 103 |
|