PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.27
Booktics – Appointment Booking Calendar for Service Businesses v1.0.27
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 +311 -174 1.0.1 → 1.0.27 View file →
@@ -1,174 +1,311 @@
1 -<?php
2 -
3 -namespace Booktics\Extensions\Controllers;
4 -
5 -use Booktics\Abstracts\Base_Rest_Controller;
6 -use Booktics\Extensions\Plugin_Manager;
7 -use WP_HTTP_Response;
8 -
9 -class Extension_Controller extends Base_Rest_Controller {
10 - /**
11 - * Store namespace of api
12 - *
13 - * @var string
14 - */
15 - protected $namespace = 'booktics/v1';
16 -
17 - /**
18 - * Store base url
19 - *
20 - * @var string
21 - */
22 - protected $base = 'extensions';
23 -
24 - /**
25 - *
26 - *
27 - * @return void
28 - */
29 - public function register_routes(): void {
30 - register_rest_route(
31 - $this->namespace, $this->base, array(
32 - array(
33 - 'methods' => \WP_REST_Server::READABLE,
34 - 'callback' => array( $this, 'get_items' ),
35 - 'permission_callback' => array(
36 - $this,
37 - 'get_items_permissions_check',
38 - ),
39 - ),
40 - )
41 - );
42 -
43 - register_rest_route(
44 - $this->namespace, $this->base, array(
45 - array(
46 - 'methods' => \WP_REST_Server::EDITABLE,
47 - 'callback' => array( $this, 'update_item' ),
48 - 'permission_callback' => array(
49 - $this,
50 - 'update_item_permissions_check',
51 - ),
52 - ),
53 - )
54 - );
55 - }
56 -
57 - /**
58 - * Check if a given request has access to get items
59 - *
60 - * @param $request
61 - *
62 - * @return bool|\WP_Error
63 - */
64 - public function get_items_permissions_check( $request ) {
65 - return true;
66 - }
67 -
68 - /**
69 - * Get all extensions
70 - *
71 - * @param $request
72 - *
73 - * @return WP_HTTP_Response
74 - */
75 - public function get_items( $request ) {
76 - $type = ! empty( $request['type'] ) ? $request['type'] : 'addon';
77 -
78 - $extensions = array_values(
79 - array_filter(
80 - Plugin_Manager::extensions(),
81 - function ( $extension ) use ( $type ) {
82 - return $extension['type'] === $type;
83 - }
84 - )
85 - );
86 -
87 - $extensions = array_map(
88 - function ( $extension ) {
89 - if ( Plugin_Manager::is_installed( $extension['name'] ) ) {
90 - $extension['status'] = 'installed';
91 - }
92 - if ( Plugin_Manager::is_activated( $extension['name'] ) ) {
93 - $extension['status'] = 'activated';
94 - }
95 -
96 - return $extension;
97 - }, $extensions
98 - );
99 -
100 - return $this->response( $extensions );
101 - }
102 -
103 - /**
104 - * Check if a given request has access to get items
105 - *
106 - * @param $request
107 - *
108 - * @return bool|\WP_Error
109 - */
110 - public function update_item_permissions_check( $request ) {
111 - return true;
112 - }
113 -
114 - /**
115 - * Enable or disable extension
116 - *
117 - * @param $request
118 - *
119 - * @return WP_HTTP_Response
120 - */
121 - public function update_item( $request ) {
122 - $input_data = json_decode( $request->get_body(), true );
123 - $name = $input_data['name'];
124 - $status = $input_data['status'];
125 -
126 - $statuses = array( 'off', 'on', 'install', 'activate', 'deactivate' );
127 - $extensions = Plugin_Manager::extensions();
128 - if ( ! $name ) {
129 - return $this->error( __( 'Please enter extension name', 'booktics' ) );
130 - }
131 -
132 - if ( ! $status ) {
133 - return $this->error( __( 'Please enter status', 'booktics' ) );
134 - }
135 -
136 - if ( ! in_array( $status, $statuses ) ) {
137 - return $this->error( __( 'Please enter status on/off', 'booktics' ) );
138 - }
139 -
140 - if ( ! isset( $extensions[ $name ] ) ) {
141 - return $this->error( __( 'Invalid extension', 'booktics' ) );
142 - }
143 - $extension = $extensions[ $name ];
144 - if ( 'addon' === $extension['type'] ) {
145 - $result = null;
146 -
147 - if ( in_array( $status, array( 'install', 'activate' ) ) ) {
148 - $result = Plugin_Manager::install_plugin( $name );
149 - $result = Plugin_Manager::activate_plugin( $name );
150 - }
151 -
152 - if ( 'deactivate' === $status && Plugin_Manager::is_activated( $name ) ) {
153 - $result = Plugin_Manager::deactivate_plugin( $name );
154 - }
155 -
156 - if ( is_wp_error( $result ) ) {
157 - return $this->error( __( 'Extension installation error', 'booktics' ) );
158 - }
159 -
160 - if ( ! $result ) {
161 - return $this->error( __( "Extension couldn't " . $status, 'booktics' ) );
162 - }
163 - }
164 - $response = Plugin_Manager::extensions()[ $name ];
165 - if ( Plugin_Manager::is_installed( $response['name'] ) ) {
166 - $response['status'] = 'installed';
167 - }
168 - if ( Plugin_Manager::is_activated( $response['name'] ) ) {
169 - $response['status'] = 'activated';
170 - }
171 -
172 - return $this->response( $response, __( 'Successfully updated', 'booktics' ) );
173 - }
174 -}
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 +}