PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.6.0
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.6.0
3.6.0 3.5.3 3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / includes / Integrations / AI / class-evf-ai-registration.php
everest-forms / includes / Integrations / AI Last commit date
class-evf-ai-ajax.php 3 days ago class-evf-ai-api.php 3 days ago class-evf-ai-form-builder.php 3 days ago class-evf-ai-loader.php 2 months ago class-evf-ai-registration.php 3 days ago
class-evf-ai-registration.php
239 lines
1 <?php
2 /**
3 * EVF AI Registration — manages site token lifecycle.
4 *
5 * Mirrors WPForms' LiteConnect pattern: the plugin silently registers with
6 * the gateway on first use and stores a site_token in wp_options.
7 * Pro users additionally activate their EVF license key to upgrade limits.
8 *
9 * Options used:
10 * evf_ai_credentials { site_token, tier, registered_at }
11 */
12
13 defined( 'ABSPATH' ) || exit;
14
15 class EVF_AI_Registration {
16
17 const OPTION_KEY = 'evf_ai_credentials';
18 const LOCK_TRANSIENT = 'evf_ai_registration_lock';
19 const VERIFY_PREFIX = 'evf_ai_verify_'; // transient prefix for ownership tokens
20 const VERIFY_TTL = 300; // 5 minutes
21
22 /**
23 * Get the stored site token, or null if not yet registered.
24 */
25 public static function get_site_token(): ?string {
26 $creds = get_option( self::OPTION_KEY );
27 return ! empty( $creds['site_token'] ) ? $creds['site_token'] : null;
28 }
29
30 /**
31 * Get the stored tier ("free" or "pro").
32 */
33 public static function get_tier(): string {
34 $creds = get_option( self::OPTION_KEY );
35 return $creds['tier'] ?? 'free';
36 }
37
38 /**
39 * True if the site has been registered with the gateway.
40 */
41 public static function is_registered(): bool {
42 return (bool) self::get_site_token();
43 }
44
45 /**
46 * True if this is a local / development site where the AI features
47 * (Create with AI, in-builder AI Assistant) should be disabled.
48 *
49 * The gateway cannot verify domain ownership for non-public hosts, so the
50 * AI features can never work on local installs — we hide them entirely
51 * instead of letting the user hit a "not available" error.
52 *
53 * Detection: WP environment type "local" OR a non-public host
54 * (localhost / loopback IP / .local / .test / .localhost TLD).
55 *
56 * @return bool
57 */
58 public static function is_local_site(): bool {
59 $host = wp_parse_url( site_url(), PHP_URL_HOST );
60 $host = $host ? strtolower( $host ) : '';
61
62 // An explicit local-gateway override (see the themegrill-ai-cloud README's "Setup —
63 // WordPress Plugin" instructions: `define('TG_AI_GATEWAY_URL', 'http://localhost:8000')`)
64 // means the developer has deliberately pointed this install at a gateway running on
65 // their own machine to test AI features end-to-end, so the heuristics below don't apply
66 // — without this the documented local-dev setup could never work on ANY local WP
67 // environment (Local by Flywheel, Valet, MAMP, …), since `WP_ENVIRONMENT_TYPE=local` and
68 // `.local`/`.test` domains are exactly what those tools use by default.
69 if ( defined( 'TG_AI_GATEWAY_URL' ) && self::is_loopback_url( TG_AI_GATEWAY_URL ) ) {
70 $is_local = false;
71 } else {
72 $is_local = ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() )
73 || in_array( $host, array( 'localhost', '127.0.0.1', '::1' ), true );
74
75 if ( ! $is_local ) {
76 foreach ( array( '.local', '.test', '.localhost' ) as $suffix ) {
77 if ( '' !== $host && substr( $host, -strlen( $suffix ) ) === $suffix ) {
78 $is_local = true;
79 break;
80 }
81 }
82 }
83 }
84
85 /**
86 * Filter whether the current site is treated as local for AI features — the final say
87 * over the heuristics above, e.g. to test the live gateway from a .local domain (set
88 * this to __return_false), or to block a host the heuristics miss.
89 *
90 * @param bool $is_local Whether the built-in heuristics consider this site local.
91 * @param string $host The detected site host.
92 */
93 return (bool) apply_filters( 'everest_forms_ai_is_local_site', $is_local, $host );
94 }
95
96 /**
97 * Whether a URL's host is a loopback address (localhost / 127.0.0.1 / ::1) — i.e. a gateway
98 * that can only possibly be running on this same machine, never a real production endpoint.
99 *
100 * @param string $url URL to inspect.
101 * @return bool
102 */
103 private static function is_loopback_url( string $url ): bool {
104 $host = wp_parse_url( $url, PHP_URL_HOST );
105 return in_array( strtolower( (string) $host ), array( 'localhost', '127.0.0.1', '::1' ), true );
106 }
107
108 /**
109 * Clear stored credentials and registration lock.
110 * Called automatically when the gateway returns "Invalid token" so the
111 * site re-registers transparently on the next request.
112 */
113 public static function clear_credentials(): void {
114 delete_option( self::OPTION_KEY );
115 delete_transient( self::LOCK_TRANSIENT );
116 }
117
118 /**
119 * Register this site with the gateway (called silently on first AI use).
120 * Uses a transient lock to prevent concurrent registration attempts.
121 *
122 * @return bool True on success.
123 */
124 public static function register(): bool {
125 // Prevent duplicate registration
126 if ( self::is_registered() ) {
127 return true;
128 }
129
130 // Transient lock — only one registration attempt per 60 seconds
131 if ( get_transient( self::LOCK_TRANSIENT ) ) {
132 return false;
133 }
134 set_transient( self::LOCK_TRANSIENT, true, 60 );
135
136 // Generate a one-time ownership token the gateway will call back to verify.
137 // The gateway hits /wp-json/everest-forms/v1/gateway-verify?token=... and we
138 // confirm the transient exists — proving this WordPress install issued the request.
139 $verify_token = wp_generate_password( 32, false );
140 set_transient( self::VERIFY_PREFIX . $verify_token, 1, self::VERIFY_TTL );
141
142 $response = EVF_AI_API::register_site( $verify_token );
143
144 if ( is_wp_error( $response ) || empty( $response['site_token'] ) ) {
145 delete_transient( self::VERIFY_PREFIX . $verify_token );
146 return false;
147 }
148
149 update_option(
150 self::OPTION_KEY,
151 array(
152 'site_token' => sanitize_text_field( $response['site_token'] ),
153 'tier' => sanitize_key( $response['tier'] ?? 'free' ),
154 'registered_at' => time(),
155 )
156 );
157
158 delete_transient( self::LOCK_TRANSIENT );
159 return true;
160 }
161
162 /**
163 * Activate pro tier using the EVF Pro license key.
164 * Called automatically when EVF Pro license is activated.
165 *
166 * @param string $license_key
167 * @return bool
168 */
169 public static function activate_pro( string $license_key ): bool {
170 $token = self::get_site_token();
171
172 // Register first if needed
173 if ( ! $token ) {
174 if ( ! self::register() ) {
175 return false;
176 }
177 $token = self::get_site_token();
178 }
179
180 $response = EVF_AI_API::activate_license( $token, $license_key );
181
182 if ( is_wp_error( $response ) || empty( $response['tier'] ) ) {
183 return false;
184 }
185
186 $creds = get_option( self::OPTION_KEY, array() );
187 $creds['tier'] = sanitize_key( $response['tier'] );
188 $creds['plan'] = sanitize_key( $response['plan'] ?? '' );
189 update_option( self::OPTION_KEY, $creds );
190
191 return 'pro' === $creds['tier'];
192 }
193
194 /**
195 * Register the public REST endpoint the gateway calls to verify domain ownership.
196 * Hooked to rest_api_init from class-evf-ai-loader.php.
197 */
198 public static function register_verify_endpoint(): void {
199 register_rest_route(
200 'everest-forms/v1',
201 '/gateway-verify',
202 array(
203 'methods' => WP_REST_Server::READABLE,
204 'callback' => array( __CLASS__, 'handle_verify_request' ),
205 'permission_callback' => '__return_true',
206 )
207 );
208 }
209
210 /**
211 * Respond to the gateway's ownership callback.
212 *
213 * The gateway sends the same verify_token we generated in register().
214 * We check the transient (one-time, 5-minute TTL) and delete it on success
215 * so the token cannot be reused.
216 *
217 * @param WP_REST_Request $request Incoming REST request.
218 * @return WP_REST_Response
219 */
220 public static function handle_verify_request( WP_REST_Request $request ): WP_REST_Response {
221 $token = sanitize_text_field( $request->get_param( 'token' ) );
222
223 if ( ! $token ) {
224 return new WP_REST_Response( array( 'valid' => false ), 400 );
225 }
226
227 $stored = get_transient( self::VERIFY_PREFIX . $token );
228
229 if ( ! $stored ) {
230 return new WP_REST_Response( array( 'valid' => false ), 403 );
231 }
232
233 // One-time use — delete immediately so the token cannot be replayed.
234 delete_transient( self::VERIFY_PREFIX . $token );
235
236 return new WP_REST_Response( array( 'valid' => true ), 200 );
237 }
238 }
239