PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.28
Booktics – Appointment Booking Calendar for Service Businesses v1.0.28
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
← All changes | core/extensions/controllers/extension-controller.php +153 -16 1.0.2 → 1.0.28 View file →
@@ -3,8 +3,9 @@
3 3 namespace Booktics\Extensions\Controllers;
4 4
5 5 use Booktics\Abstracts\Base_Rest_Controller;
6 6 use Booktics\Extensions\Plugin_Manager;
7 +use Arraytics\ToolsSdk\PluginManager as SdkPluginManager;
7 8 use WP_HTTP_Response;
8 9
9 10 class Extension_Controller extends Base_Rest_Controller {
10 11 /**
@@ -61,9 +62,9 @@
61 62 *
62 63 * @return bool|\WP_Error
63 64 */
64 65 public function get_items_permissions_check( $request ) {
65 - return true;
66 + return current_user_can( 'manage_options' );
66 67 }
67 68
68 69 /**
69 70 * Get all extensions
@@ -72,9 +73,9 @@
72 73 *
73 74 * @return WP_HTTP_Response
74 75 */
75 76 public function get_items( $request ) {
76 - $type = ! empty( $request['type'] ) ? $request['type'] : 'addon';
77 + $type = ! empty( $request['type'] ) ? sanitize_key( $request['type'] ) : 'addon';
77 78
78 79 $extensions = array_values(
79 80 array_filter(
80 81 Plugin_Manager::extensions(),
@@ -85,13 +86,14 @@
85 86 );
86 87
87 88 $extensions = array_map(
88 89 function ( $extension ) {
89 - if ( Plugin_Manager::is_installed( $extension['name'] ) ) {
90 - $extension['status'] = 'installed';
90 + $extension['status'] = 'install';
91 + if ( SdkPluginManager::is_installed( $extension['slug'] ) ) {
92 + $extension['status'] = 'activate';
91 93 }
92 - if ( Plugin_Manager::is_activated( $extension['name'] ) ) {
93 - $extension['status'] = 'activated';
94 + if ( SdkPluginManager::is_activated( $extension['slug'] ) ) {
95 + $extension['status'] = 'deactivate';
94 96 }
95 97
96 98 return $extension;
97 99 }, $extensions
@@ -107,9 +109,9 @@
107 109 *
108 110 * @return bool|\WP_Error
109 111 */
110 112 public function update_item_permissions_check( $request ) {
111 - return true;
113 + return current_user_can( 'manage_options' );
112 114 }
113 115
114 116 /**
115 117 * Enable or disable extension
@@ -140,18 +142,35 @@
140 142 if ( ! isset( $extensions[ $name ] ) ) {
141 143 return $this->error( __( 'Invalid extension', 'booktics' ) );
142 144 }
143 145 $extension = $extensions[ $name ];
146 +
147 + // Strict, so a "1" or "true" string never counts as agreement.
148 + $aisentic_consent = 'aisentic' === $name && true === ( $input_data['consent'] ?? null );
149 + $aisentic_email = '';
150 +
151 + // The Ask AI setup dialog sends an edited email. Reject a bad one before
152 + // anything is installed.
153 + if ( $aisentic_consent && isset( $input_data['email'] ) ) {
154 + $aisentic_email = sanitize_email( (string) $input_data['email'] );
155 +
156 + if ( ! is_email( $aisentic_email ) ) {
157 + return $this->error( __( 'Enter a valid email address.', 'booktics' ), 400 );
158 + }
159 + }
160 +
144 161 if ( 'addon' === $extension['type'] ) {
145 162 $result = null;
146 163
147 164 if ( in_array( $status, array( 'install', 'activate' ) ) ) {
148 - $result = Plugin_Manager::install_plugin( $name );
149 - $result = Plugin_Manager::activate_plugin( $name );
165 + $result = Plugin_Manager::install_plugin( $extension['slug'] );
166 + if ( $result ) {
167 + $result = Plugin_Manager::activate_plugin( $extension['slug'] );
168 + }
150 169 }
151 170
152 - if ( 'deactivate' === $status && Plugin_Manager::is_activated( $name ) ) {
153 - $result = Plugin_Manager::deactivate_plugin( $name );
171 + if ( 'deactivate' === $status && Plugin_Manager::is_activated( $extension['slug'] ) ) {
172 + $result = Plugin_Manager::deactivate_plugin( $extension['slug'] );
154 173 }
155 174
156 175 if ( is_wp_error( $result ) ) {
157 176 return $this->error( __( 'Extension installation error', 'booktics' ) );
@@ -157,18 +176,136 @@
157 176 return $this->error( __( 'Extension installation error', 'booktics' ) );
158 177 }
159 178
160 179 if ( ! $result ) {
161 - return $this->error( __( "Extension couldn't " . $status, 'booktics' ) );
180 + // translators: %s is the action performed on the extension (install, activate, deactivate, etc.)
181 + return $this->error( sprintf( __( 'Extension couldn\'t %s', 'booktics' ), $status ) );
162 182 }
163 183 }
184 +
185 + if ( 'plugin' === $extension['type'] ) {
186 + $result = null;
187 +
188 + // Only people who could do this by hand from the Plugins screen.
189 + if ( 'install' === $status && ! current_user_can( 'install_plugins' ) ) {
190 + return $this->error( __( 'You are not allowed to install plugins.', 'booktics' ), 403 );
191 + }
192 +
193 + if ( 'install' !== $status && ! current_user_can( 'activate_plugins' ) ) {
194 + return $this->error( __( 'You are not allowed to manage plugins.', 'booktics' ), 403 );
195 + }
196 +
197 + if ( 'install' === $status ) {
198 + // Prefer the extension's own download_link (e.g. GitHub release) so a
199 + // non-wordpress.org URL is not shadowed by the SDK's hardcoded wp.org zip.
200 + $result = ! empty( $extension['download_link'] )
201 + ? Plugin_Manager::install_plugin( $extension['slug'] )
202 + : SdkPluginManager::install_plugin( $extension['slug'] );
203 + if ( $result ) {
204 + $result = SdkPluginManager::activate_plugin( $extension['slug'] );
205 + }
206 + } elseif ( 'activate' === $status ) {
207 + $result = SdkPluginManager::activate_plugin( $extension['slug'] );
208 + } elseif ( 'deactivate' === $status ) {
209 + $result = SdkPluginManager::deactivate_plugin( $extension['slug'] );
210 + }
211 +
212 + if ( is_wp_error( $result ) ) {
213 + return $this->error( __( 'Plugin operation error', 'booktics' ) );
214 + }
215 +
216 + if ( false === $result ) {
217 + // translators: %s is the action performed on the plugin (install, activate, deactivate, etc.)
218 + return $this->error( sprintf( __( 'Plugin couldn\'t %s', 'booktics' ), $status ) );
219 + }
220 + }
221 +
164 222 $response = Plugin_Manager::extensions()[ $name ];
165 - if ( Plugin_Manager::is_installed( $response['name'] ) ) {
166 - $response['status'] = 'installed';
223 +
224 + // Nothing reaches Aisentic's provider on activation alone. The handshake only
225 + // runs when the caller ticked the consent box and the plugin really is active.
226 + if ( 'aisentic' === $name
227 + && in_array( $status, array( 'install', 'activate' ), true )
228 + && $aisentic_consent
229 + && SdkPluginManager::is_activated( $extension['slug'] ) ) {
230 + // Snapshot before the handshake so the caller can tell a fresh
231 + // registration (tokens just granted) from re-activating a site that
232 + // was already connected (no new tokens).
233 + $was_registered = booktics_aisentic_is_registered();
234 +
235 + $this->register_aisentic_site(
236 + $aisentic_email,
237 + sanitize_text_field( (string) ( $input_data['account_name'] ?? '' ) )
238 + );
239 +
240 + $is_registered = booktics_aisentic_is_registered();
241 +
242 + $response['aisentic_registered'] = $is_registered;
243 +
244 + // True only when this request is what connected the site, so the
245 + // "150K tokens added" message never fires on a plain re-activation.
246 + $response['aisentic_newly_registered'] = $is_registered && ! $was_registered;
167 247 }
168 - if ( Plugin_Manager::is_activated( $response['name'] ) ) {
169 - $response['status'] = 'activated';
248 +
249 + $response['status'] = 'install';
250 + if ( SdkPluginManager::is_installed( $response['slug'] ) ) {
251 + $response['status'] = 'activate';
170 252 }
253 + if ( SdkPluginManager::is_activated( $response['slug'] ) ) {
254 + $response['status'] = 'deactivate';
255 + }
171 256
172 257 return $this->response( $response, __( 'Successfully updated', 'booktics' ) );
258 + }
259 +
260 + /**
261 + * Record consent and hand the site identity to Aisentic.
262 + *
263 + * Only ever called from behind the consent gate in update_item(). The action is
264 + * fire and forget: Aisentic owns the provider call, so a provider outage cannot
265 + * fail the activation the user actually asked for.
266 + *
267 + * @param string $email Email the user confirmed in the Ask AI dialog. Empty uses the business email.
268 + * @param string $name Account name sent by the Ask AI dialog. Empty uses the business name.
269 + *
270 + * @return void
271 + */
272 + private function register_aisentic_site( string $email = '', string $name = '' ): void {
273 + // Older Aisentic builds ship no listener, so the handshake would go nowhere.
274 + if ( ! class_exists( 'Aisentic\Api\Services\Registration_Service' ) ) {
275 + return;
276 + }
277 +
278 + $identity = booktics_aisentic_identity();
279 +
280 + if ( $email ) {
281 + $identity['email'] = $email;
282 + }
283 +
284 + if ( $name ) {
285 + $identity['name'] = $name;
286 + }
287 +
288 + // Fail closed. An account cannot be minted without a real address.
289 + if ( empty( $identity['email'] ) ) {
290 + return;
291 + }
292 +
293 + update_option(
294 + 'booktics_aisentic_consent', array(
295 + 'agreed' => true,
296 + 'time' => gmdate( 'c' ),
297 + 'user_id' => get_current_user_id(),
298 + 'email' => $identity['email'],
299 + )
300 + );
301 +
302 + /**
303 + * Fires once the admin has opted in to sharing site details with Aisentic.
304 + *
305 + * @param string $name Business name, falling back to the site title.
306 + * @param string $email Business email, falling back to the admin email.
307 + * @param string $site_url Site URL.
308 + */
309 + do_action( 'booktics/aisentic/register_site', $identity['name'], $identity['email'], $identity['site_url'] );
173 310 }
174 311 }