PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.29
Booktics – Appointment Booking Calendar for Service Businesses v1.0.29
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
booktics / core / extensions / controllers / extension-controller.php

extension-controller.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.29, at core/extensions/controllers/extension-controller.php

312 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Extensions\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Extensions\Plugin_Manager;
7 use Arraytics\ToolsSdk\PluginManager as SdkPluginManager;
8 use WP_HTTP_Response;
9
10 class Extension_Controller extends Base_Rest_Controller {
11 /**
12 * Store namespace of api
13 *
14 * @var string
15 */
16 protected $namespace = 'booktics/v1';
17
18 /**
19 * Store base url
20 *
21 * @var string
22 */
23 protected $base = 'extensions';
24
25 /**
26 *
27 *
28 * @return void
29 */
30 public function register_routes(): void {
31 register_rest_route(
32 $this->namespace, $this->base, array(
33 array(
34 'methods' => \WP_REST_Server::READABLE,
35 'callback' => array( $this, 'get_items' ),
36 'permission_callback' => array(
37 $this,
38 'get_items_permissions_check',
39 ),
40 ),
41 )
42 );
43
44 register_rest_route(
45 $this->namespace, $this->base, array(
46 array(
47 'methods' => \WP_REST_Server::EDITABLE,
48 'callback' => array( $this, 'update_item' ),
49 'permission_callback' => array(
50 $this,
51 'update_item_permissions_check',
52 ),
53 ),
54 )
55 );
56 }
57
58 /**
59 * Check if a given request has access to get items
60 *
61 * @param $request
62 *
63 * @return bool|\WP_Error
64 */
65 public function get_items_permissions_check( $request ) {
66 return current_user_can( 'manage_options' );
67 }
68
69 /**
70 * Get all extensions
71 *
72 * @param $request
73 *
74 * @return WP_HTTP_Response
75 */
76 public function get_items( $request ) {
77 $type = ! empty( $request['type'] ) ? sanitize_key( $request['type'] ) : 'addon';
78
79 $extensions = array_values(
80 array_filter(
81 Plugin_Manager::extensions(),
82 function ( $extension ) use ( $type ) {
83 return $extension['type'] === $type;
84 }
85 )
86 );
87
88 $extensions = array_map(
89 function ( $extension ) {
90 $extension['status'] = 'install';
91 if ( SdkPluginManager::is_installed( $extension['slug'] ) ) {
92 $extension['status'] = 'activate';
93 }
94 if ( SdkPluginManager::is_activated( $extension['slug'] ) ) {
95 $extension['status'] = 'deactivate';
96 }
97
98 return $extension;
99 }, $extensions
100 );
101
102 return $this->response( $extensions );
103 }
104
105 /**
106 * Check if a given request has access to get items
107 *
108 * @param $request
109 *
110 * @return bool|\WP_Error
111 */
112 public function update_item_permissions_check( $request ) {
113 return current_user_can( 'manage_options' );
114 }
115
116 /**
117 * Enable or disable extension
118 *
119 * @param $request
120 *
121 * @return WP_HTTP_Response
122 */
123 public function update_item( $request ) {
124 $input_data = json_decode( $request->get_body(), true );
125 $name = $input_data['name'];
126 $status = $input_data['status'];
127
128 $statuses = array( 'off', 'on', 'install', 'activate', 'deactivate' );
129 $extensions = Plugin_Manager::extensions();
130 if ( ! $name ) {
131 return $this->error( __( 'Please enter extension name', 'booktics' ) );
132 }
133
134 if ( ! $status ) {
135 return $this->error( __( 'Please enter status', 'booktics' ) );
136 }
137
138 if ( ! in_array( $status, $statuses ) ) {
139 return $this->error( __( 'Please enter status on/off', 'booktics' ) );
140 }
141
142 if ( ! isset( $extensions[ $name ] ) ) {
143 return $this->error( __( 'Invalid extension', 'booktics' ) );
144 }
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
161 if ( 'addon' === $extension['type'] ) {
162 $result = null;
163
164 if ( in_array( $status, array( 'install', 'activate' ) ) ) {
165 $result = Plugin_Manager::install_plugin( $extension['slug'] );
166 if ( $result ) {
167 $result = Plugin_Manager::activate_plugin( $extension['slug'] );
168 }
169 }
170
171 if ( 'deactivate' === $status && Plugin_Manager::is_activated( $extension['slug'] ) ) {
172 $result = Plugin_Manager::deactivate_plugin( $extension['slug'] );
173 }
174
175 if ( is_wp_error( $result ) ) {
176 return $this->error( __( 'Extension installation error', 'booktics' ) );
177 }
178
179 if ( ! $result ) {
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 ) );
182 }
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
222 $response = Plugin_Manager::extensions()[ $name ];
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;
247 }
248
249 $response['status'] = 'install';
250 if ( SdkPluginManager::is_installed( $response['slug'] ) ) {
251 $response['status'] = 'activate';
252 }
253 if ( SdkPluginManager::is_activated( $response['slug'] ) ) {
254 $response['status'] = 'deactivate';
255 }
256
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'] );
310 }
311 }
312