PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.2.13
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.2.13
2.2.13 2.2.12 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 trunk 1.0.0 1.0.1 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2
table-builder-block / includes / Core / RestApi.php

RestApi.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables 2.2.13, at includes/Core/RestApi.php

478 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API fallback for plugin dependency management
4 *
5 * @package TableKit
6 */
7
8 namespace TableBuilder\Core;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * REST API fallback when tablekit-essential is not active.
14 * Mimics tablekit-essential behavior for compatibility.
15 *
16 * @since 1.0.0
17 */
18 class RestApi {
19 use \TableBuilder\Traits\Singleton;
20
21 /**
22 * Hooks route registration and plugin-cache invalidation into WordPress.
23 */
24 public function __construct() {
25 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
26 add_action( 'activated_plugin', array( $this, 'clear_plugin_cache' ) );
27 add_action( 'deactivated_plugin', array( $this, 'clear_plugin_cache' ) );
28 }
29
30 /**
31 * Clears the cached plugin-status transient when plugins are (de)activated.
32 *
33 * @return void
34 */
35 public function clear_plugin_cache() {
36 delete_transient( 'tablekit_plugin_data' );
37 }
38
39 /**
40 * Registers the tablekit/v1 fallback REST routes, only if the
41 * tablekit-essential plugin (which normally provides them) isn't active.
42 *
43 * @return void
44 */
45 public function register_routes() {
46 if ( $this->is_tablekit_essential_active() ) {
47 return;
48 }
49
50 register_rest_route(
51 'tablekit/v1',
52 '/table-manager-api/check-plugins',
53 array(
54 'methods' => 'POST',
55 'callback' => array( $this, 'check_plugins_status' ),
56 'permission_callback' => function () {
57 return current_user_can( 'edit_posts' ); },
58 )
59 );
60
61 register_rest_route(
62 'tablekit/v1',
63 '/table-manager-api/activate-plugins',
64 array(
65 'methods' => 'POST',
66 'callback' => array( $this, 'activate_plugins' ),
67 'permission_callback' => function () {
68 return current_user_can( 'activate_plugins' ); },
69 )
70 );
71
72 register_rest_route(
73 'tablekit/v1',
74 '/table-manager-api/install-plugins',
75 array(
76 'methods' => 'POST',
77 'callback' => array( $this, 'install_plugins' ),
78 'permission_callback' => function () {
79 return current_user_can( 'install_plugins' ); },
80 )
81 );
82
83 register_rest_route(
84 'tablekit/v1',
85 '/table-manager-api/check-pro-license',
86 array(
87 'methods' => 'POST',
88 'callback' => array( $this, 'check_pro_license' ),
89 'permission_callback' => function () {
90 return current_user_can( 'activate_plugins' ); },
91 )
92 );
93 }
94
95 /**
96 * Checks whether the tablekit-essential plugin is active.
97 *
98 * @return bool True if tablekit-essential is active.
99 */
100 private function is_tablekit_essential_active() {
101 if ( ! function_exists( 'is_plugin_active' ) ) {
102 require_once ABSPATH . 'wp-admin/includes/plugin.php';
103 }
104 return is_plugin_active( 'tablekit-essential/tablekit-essential.php' );
105 }
106
107 /**
108 * REST callback: report install/active status for a list of plugin slugs.
109 *
110 * @param \WP_REST_Request $request Request with a JSON "plugins" array param.
111 * @return \WP_REST_Response Status per slug: installed/active/state/plugin_file.
112 */
113 public function check_plugins_status( $request ) {
114 $plugins = $request->get_json_params()['plugins'] ?? array();
115 if ( empty( $plugins ) ) {
116 return new \WP_REST_Response(
117 array(
118 'status' => 'error',
119 'message' => 'No plugins specified.',
120 ),
121 400
122 );
123 }
124
125 // Clear plugin cache to ensure fresh data (critical for detecting deactivated plugins on servers).
126 wp_cache_delete( 'plugins', 'plugins' );
127
128 if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'is_plugin_active' ) ) {
129 require_once ABSPATH . 'wp-admin/includes/plugin.php';
130 }
131
132 $all_plugins = get_plugins();
133 $status = array();
134
135 foreach ( $plugins as $slug ) {
136 $slug = sanitize_text_field( $slug );
137 if ( empty( $slug ) ) {
138 continue;
139 }
140
141 $plugin_file = null;
142 foreach ( $all_plugins as $plugin_path => $plugin_data ) {
143 if ( 0 === strpos( $plugin_path, $slug . '/' ) || $slug . '.php' === $plugin_path ) {
144 $plugin_file = $plugin_path;
145 break;
146 }
147 }
148
149 $is_installed = null !== $plugin_file;
150 $is_active = $is_installed && is_plugin_active( $plugin_file );
151
152 $status[ $slug ] = array(
153 'installed' => $is_installed,
154 'active' => $is_active,
155 'state' => $is_installed ? ( $is_active ? 'installed_active' : 'installed_inactive' ) : 'not_installed',
156 'plugin_file' => $plugin_file,
157 );
158 }
159
160 return new \WP_REST_Response(
161 array(
162 'status' => 'success',
163 'plugins' => $status,
164 ),
165 200
166 );
167 }
168
169 /**
170 * REST callback: activate a list of already-installed plugins by slug.
171 *
172 * @param \WP_REST_Request $request Request with a JSON "plugins" array param.
173 * @return \WP_REST_Response Activation results: which slugs succeeded/failed.
174 */
175 public function activate_plugins( $request ) {
176 $plugins = $request->get_json_params()['plugins'] ?? array();
177 if ( empty( $plugins ) ) {
178 return new \WP_REST_Response(
179 array(
180 'status' => 'error',
181 'message' => 'No plugins specified.',
182 ),
183 400
184 );
185 }
186
187 if ( ! function_exists( 'activate_plugin' ) ) {
188 require_once ABSPATH . 'wp-admin/includes/plugin.php';
189 }
190
191 wp_cache_delete( 'plugins', 'plugins' );
192
193 // Set transient to prevent redirect during programmatic activation.
194 set_transient( 'tablekit_skip_activation_redirect', true, 60 );
195
196 $all_plugins = get_plugins();
197 $activated = array();
198 $failed = array();
199
200 foreach ( $plugins as $slug ) {
201 $slug = sanitize_text_field( $slug );
202 if ( empty( $slug ) ) {
203 continue;
204 }
205
206 $plugin_file = null;
207 foreach ( $all_plugins as $plugin_path => $plugin_data ) {
208 if ( strpos( $plugin_path, $slug . '/' ) === 0 || $plugin_path === $slug . '.php' ) {
209 $plugin_file = $plugin_path;
210 break;
211 }
212 }
213
214 if ( ! $plugin_file ) {
215 $failed[] = array(
216 'slug' => $slug,
217 'error' => 'Plugin not found',
218 );
219 continue;
220 }
221
222 if ( is_plugin_active( $plugin_file ) ) {
223 $activated[] = $slug;
224 continue;
225 }
226
227 $result = activate_plugin( $plugin_file, '', false, true ); // Silent activation.
228 if ( is_wp_error( $result ) ) {
229 $failed[] = array(
230 'slug' => $slug,
231 'error' => $result->get_error_message(),
232 );
233 } else {
234 $activated[] = $slug;
235 }
236 }
237
238 // Clean up transient.
239 delete_transient( 'tablekit_skip_activation_redirect' );
240
241 // Clear plugin cache after activation.
242 $this->clear_plugin_cache();
243
244 return new \WP_REST_Response(
245 array(
246 'status' => empty( $failed ) ? 'success' : 'partial',
247 'activated' => $activated,
248 'failed' => $failed,
249 ),
250 200
251 );
252 }
253
254 /**
255 * REST callback: install (and activate) a list of plugins by slug from
256 * the WordPress.org plugin directory. Slugs containing "-pro" are
257 * rejected, since pro plugins must be purchased/uploaded manually.
258 *
259 * @param \WP_REST_Request $request Request with a JSON "plugins" array param.
260 * @return \WP_REST_Response Installation results: which slugs failed and why.
261 */
262 public function install_plugins( $request ) {
263 $plugins = $request->get_json_params()['plugins'] ?? array();
264 if ( empty( $plugins ) ) {
265 return new \WP_REST_Response(
266 array(
267 'status' => 'error',
268 'message' => 'No plugins specified.',
269 ),
270 400
271 );
272 }
273
274 if ( ! function_exists( 'plugins_api' ) ) {
275 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
276 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
277 require_once ABSPATH . 'wp-admin/includes/file.php';
278 }
279
280 $failed = array();
281
282 foreach ( $plugins as $slug ) {
283 $slug = sanitize_text_field( $slug );
284 if ( empty( $slug ) ) {
285 continue;
286 }
287
288 // Check if pro plugin.
289 if ( strpos( $slug, '-pro' ) !== false ) {
290 $failed[] = array(
291 'slug' => $slug,
292 'error' => 'Pro plugins must be purchased manually',
293 );
294 continue;
295 }
296
297 // Check if already installed.
298 $all_plugins = get_plugins();
299 $plugin_file = null;
300 foreach ( $all_plugins as $plugin_path => $plugin_data ) {
301 if ( strpos( $plugin_path, $slug . '/' ) === 0 ) {
302 $plugin_file = $plugin_path;
303 break;
304 }
305 }
306
307 if ( $plugin_file ) {
308 // Already installed, just activate.
309 if ( ! is_plugin_active( $plugin_file ) ) {
310 set_transient( 'tablekit_skip_activation_redirect', true, 60 ); // Set transient to prevent redirect.
311 activate_plugin( $plugin_file, '', false, true ); // Silent activation.
312 delete_transient( 'tablekit_skip_activation_redirect' );
313 }
314 continue;
315 }
316
317 // Install plugin.
318 $api = plugins_api(
319 'plugin_information',
320 array(
321 'slug' => $slug,
322 'fields' => array( 'sections' => false ),
323 )
324 );
325
326 if ( is_wp_error( $api ) ) {
327 $failed[] = array(
328 'slug' => $slug,
329 'error' => $api->get_error_message(),
330 );
331 continue;
332 }
333
334 $upgrader = new \Plugin_Upgrader( new \WP_Ajax_Upgrader_Skin() );
335 $result = $upgrader->install( $api->download_link );
336
337 if ( is_wp_error( $result ) ) {
338 $failed[] = array(
339 'slug' => $slug,
340 'error' => $result->get_error_message(),
341 );
342 } elseif ( false === $result ) {
343 $failed[] = array(
344 'slug' => $slug,
345 'error' => 'Installation failed',
346 );
347 }
348 }
349
350 // Clear plugin cache after installation.
351 $this->clear_plugin_cache();
352
353 return new \WP_REST_Response(
354 array(
355 'status' => empty( $failed ) ? 'success' : 'partial',
356 'failed' => $failed,
357 ),
358 200
359 );
360 }
361
362 /**
363 * REST callback: check whether GutenKit Pro's license is active
364 * (license checks are currently only supported for that plugin).
365 *
366 * @param \WP_REST_Request $request Request with a JSON "plugins" array param.
367 * @return \WP_REST_Response License status.
368 */
369 public function check_pro_license( $request ) {
370 $plugins = $request->get_json_params()['plugins'] ?? array();
371 if ( empty( $plugins ) ) {
372 return new \WP_REST_Response(
373 array(
374 'status' => 'error',
375 'message' => 'No plugins specified.',
376 ),
377 400
378 );
379 }
380
381 // Check if this is gutenkit-blocks-addon-pro.
382 $is_gutenkit_pro = false;
383 foreach ( $plugins as $slug ) {
384 if ( strpos( $slug, 'gutenkit-blocks-addon-pro' ) !== false ) {
385 $is_gutenkit_pro = true;
386 break;
387 }
388 }
389
390 if ( ! $is_gutenkit_pro ) {
391 return new \WP_REST_Response(
392 array(
393 'status' => 'error',
394 'license_valid' => false,
395 'message' => 'License check is only available for GutenKit Pro.',
396 ),
397 400
398 );
399 }
400
401 // Check GutenKit license status.
402 // GutenKit uses these options to store license info.
403 $oppai = get_option( '__gutenkit_oppai__', '' );
404 $key = get_option( '__gutenkit_license_key__', '' );
405
406 $license_valid = ! empty( $oppai ) && ! empty( $key );
407
408 if ( ! $license_valid ) {
409 return new \WP_REST_Response(
410 array(
411 'status' => 'error',
412 'license_valid' => false,
413 'message' => 'Pro plugin license is not active. Please activate your license at GutenKit → Settings → License to use pro features.',
414 ),
415 200
416 );
417 }
418
419 return new \WP_REST_Response(
420 array(
421 'status' => 'success',
422 'license_valid' => true,
423 'message' => 'License is valid.',
424 ),
425 200
426 );
427 }
428
429 /**
430 * Builds the data localized to the editor as `tablekitEssential`, mirroring
431 * the shape the tablekit-essential plugin provides, so the editor JS
432 * doesn't need to branch on which one is active. Cached for 12 hours.
433 *
434 * @return array{apiUrl:string,restNonce:string,ajaxNonce:string,activePlugins:string[],installedPlugins:string[],requiredPlugins:string[]}
435 */
436 public static function get_localized_data() {
437 // Check cache first (expires after 12 hours).
438 $cached_data = get_transient( 'tablekit_plugin_data' );
439 if ( false !== $cached_data && is_array( $cached_data ) ) {
440 // Update dynamic nonces.
441 $cached_data['restNonce'] = wp_create_nonce( 'wp_rest' );
442 $cached_data['ajaxNonce'] = wp_create_nonce( 'tablekit_activate_nonce' );
443 return $cached_data;
444 }
445
446 if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'is_plugin_active' ) ) {
447 require_once ABSPATH . 'wp-admin/includes/plugin.php';
448 }
449
450 $all_plugins = get_plugins();
451 $active_plugins = array();
452 $installed_plugins = array();
453
454 foreach ( $all_plugins as $plugin_path => $plugin_data ) {
455 $slug = explode( '/', $plugin_path )[0];
456 $installed_plugins[] = $slug;
457
458 if ( is_plugin_active( $plugin_path ) ) {
459 $active_plugins[] = $slug;
460 }
461 }
462
463 $data = array(
464 'apiUrl' => rest_url( 'tablekit/v1/' ),
465 'restNonce' => wp_create_nonce( 'wp_rest' ),
466 'ajaxNonce' => wp_create_nonce( 'tablekit_activate_nonce' ),
467 'activePlugins' => array_unique( $active_plugins ),
468 'installedPlugins' => array_unique( $installed_plugins ),
469 'requiredPlugins' => array( 'gutenkit-blocks-addon', 'gutenkit-blocks-addon-pro' ),
470 );
471
472 // Cache for 12 hours.
473 set_transient( 'tablekit_plugin_data', $data, 12 * HOUR_IN_SECONDS );
474
475 return $data;
476 }
477 }
478