PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Ai / AiAvailability.php

AiAvailability.php in The Innovative Form Builder – IvyForms 1.5, at backend/src/Services/Ai/AiAvailability.php

207 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @copyright © Melograno Venture Studio. All rights reserved.
5 * @licence See LICENCE.md for license details.
6 */
7
8 namespace IvyForms\Services\Ai;
9
10 /**
11 * Reports whether WordPress AI Client can generate forms for IvyForms.
12 */
13 class AiAvailability
14 {
15 public const STATUS_AVAILABLE = 'available';
16 public const STATUS_WORDPRESS_UPDATE_REQUIRED = 'wordpress_update_required';
17 public const STATUS_AI_DISABLED = 'ai_disabled';
18 public const STATUS_AI_CLIENT_MISSING = 'ai_client_missing';
19 public const STATUS_TEXT_GENERATION_UNAVAILABLE = 'text_generation_unavailable';
20
21 private const AVAILABILITY_CACHE_TRANSIENT = 'ivyforms_ai_text_generation_available';
22
23 /** Short TTL — capability checks may inspect remote provider metadata. */
24 private const AVAILABILITY_CACHE_TTL = 60;
25
26 /** @var bool|null Request-scoped memo of the text-generation capability check. */
27 private static $textGenerationMemo = null;
28
29 public static function isSupported(): bool
30 {
31 return function_exists('wp_supports_ai')
32 && wp_supports_ai()
33 && function_exists('wp_ai_client_prompt');
34 }
35
36 /**
37 * Cached text-generation capability check for admin/UI.
38 *
39 * Never probes providers: missing/expired cache is treated as unavailable.
40 * Call refreshTextGenerationSupport() outside render paths to warm the cache.
41 */
42 public static function hasTextGenerationSupport(): bool
43 {
44 if (!self::isSupported()) {
45 return false;
46 }
47
48 if (self::$textGenerationMemo !== null) {
49 return self::$textGenerationMemo;
50 }
51
52 $cached = get_transient(self::AVAILABILITY_CACHE_TRANSIENT);
53 if ($cached === '1' || $cached === '0') {
54 self::$textGenerationMemo = $cached === '1';
55
56 return self::$textGenerationMemo;
57 }
58
59 return false;
60 }
61
62 /**
63 * Fresh capability check for generate requests and non-render warm paths.
64 */
65 public static function refreshTextGenerationSupport(): bool
66 {
67 if (!self::isSupported()) {
68 return false;
69 }
70
71 $supported = self::detectTextGenerationSupport();
72 set_transient(
73 self::AVAILABILITY_CACHE_TRANSIENT,
74 $supported ? '1' : '0',
75 self::AVAILABILITY_CACHE_TTL
76 );
77 self::$textGenerationMemo = $supported;
78
79 return $supported;
80 }
81
82 /**
83 * Warm the capability cache when missing/expired, without forcing a probe every request.
84 */
85 public static function maybeRefreshTextGenerationSupport(): void
86 {
87 if (!self::isSupported()) {
88 return;
89 }
90
91 $cached = get_transient(self::AVAILABILITY_CACHE_TRANSIENT);
92 if ($cached === '1' || $cached === '0') {
93 return;
94 }
95
96 self::refreshTextGenerationSupport();
97 }
98
99 /**
100 * True when AI can be used end-to-end for form generation.
101 */
102 public static function isAvailable(): bool
103 {
104 return self::getStatus() === self::STATUS_AVAILABLE;
105 }
106
107 /**
108 * Resolve the AI status using the cached text-generation capability.
109 */
110 public static function getStatus(): string
111 {
112 $preliminaryStatus = self::getPreliminaryStatus();
113 if ($preliminaryStatus !== null) {
114 return $preliminaryStatus;
115 }
116
117 return self::hasTextGenerationSupport()
118 ? self::STATUS_AVAILABLE
119 : self::STATUS_TEXT_GENERATION_UNAVAILABLE;
120 }
121
122 /**
123 * Resolve the AI status, forcing a fresh text-generation capability probe.
124 */
125 public static function getRefreshedStatus(): string
126 {
127 $preliminaryStatus = self::getPreliminaryStatus();
128 if ($preliminaryStatus !== null) {
129 return $preliminaryStatus;
130 }
131
132 return self::refreshTextGenerationSupport()
133 ? self::STATUS_AVAILABLE
134 : self::STATUS_TEXT_GENERATION_UNAVAILABLE;
135 }
136
137 /**
138 * Environment-level status that does not depend on the text-generation probe.
139 *
140 * @return string|null A terminal status, or null when text-generation capability must be checked.
141 */
142 private static function getPreliminaryStatus(): ?string
143 {
144 if (!function_exists('wp_supports_ai')) {
145 return self::STATUS_WORDPRESS_UPDATE_REQUIRED;
146 }
147
148 if (!wp_supports_ai()) {
149 return self::STATUS_AI_DISABLED;
150 }
151
152 if (!function_exists('wp_ai_client_prompt')) {
153 return self::STATUS_AI_CLIENT_MISSING;
154 }
155
156 return null;
157 }
158
159 /**
160 * Admin URL for WordPress Connectors / AI provider settings.
161 */
162 public static function getConnectorsSettingsUrl(): string
163 {
164 return admin_url('options-connectors.php');
165 }
166
167 public static function getWordPressUpdateUrl(): string
168 {
169 return current_user_can('update_core') ? self_admin_url('update-core.php') : '';
170 }
171
172 public static function getAiPluginInstallUrl(): string
173 {
174 if (!current_user_can('install_plugins')) {
175 return '';
176 }
177
178 return self_admin_url('plugin-install.php?s=AI&tab=search&type=term');
179 }
180
181 /**
182 * Whether AI may generate multi-page forms (Pro gate; default false).
183 */
184 public static function canUseMultipage(): bool
185 {
186 /**
187 * Gate AI multipage generation. Lite default is false; Pro enables when licensed.
188 *
189 * @since 0.1.0
190 *
191 * @param bool $allowed
192 * @return bool
193 */
194 return (bool) apply_filters('ivyforms/ai/can_use_multipage', false);
195 }
196
197 private static function detectTextGenerationSupport(): bool
198 {
199 try {
200 return wp_ai_client_prompt('IvyForms capability check')
201 ->is_supported_for_text_generation();
202 } catch (\Throwable $exception) {
203 return false;
204 }
205 }
206 }
207