PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
2.7.1 2.7.0 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.3 2.3.2 2.3.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 All 130 releases
wdesignkit / includes / abilities / utilities / wdesignkit-dependencies.php

wdesignkit-dependencies.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.7.0, at includes/abilities/utilities/wdesignkit-dependencies.php

487 lines 18.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abilities: Check and install plugin dependencies for WDesignKit widgets.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/check-dependencies', [
13 'label' => __('Check Widget Dependencies', 'wdesignkit'),
14 'description' => __(
15 'Checks if required plugins and themes are installed and active for WDesignKit widgets. Verifies Elementor, The Plus Addons, Nexter, Bricks, and other dependencies.',
16 'wdesignkit',
17 ),
18 'category' => 'wdesignkit',
19 'input_schema' => [
20 'type' => 'object',
21 'properties' => [
22 'builder' => [
23 'type' => 'string',
24 'description' => 'Check dependencies for a specific builder. Leave empty to check all.',
25 'enum' => ['', 'elementor', 'gutenberg', 'gutenberg_core', 'bricks'],
26 ],
27 ],
28 'additionalProperties' => false,
29 ],
30 'output_schema' => [
31 'type' => 'object',
32 'properties' => [
33 'success' => ['type' => 'boolean'],
34 'dependencies' => ['type' => 'array'],
35 ],
36 ],
37 'execute_callback' => 'wdesignkit_mcp_check_dependencies',
38 'permission_callback' => 'wdesignkit_mcp_permission_callback',
39 'meta' => [
40 'public' => true,
41 'show_in_rest' => true,
42 'mcp' => ['public' => true],
43 'annotations' => [
44 'instructions' => implode("\n", [
45 'Checks plugin/theme dependencies for WDesignKit widget building.',
46 'Returns status of each dependency: active, installed_inactive, or not_installed.',
47 '"installed_inactive" means the plugin/theme exists on disk but is not currently enabled.',
48 'Each entry includes version (installed version string), minimum_required_version (minimum needed), and is_version_compatible (bool or null when not installed).',
49 'is_version_compatible is null when the dependency is not installed (no version to compare).',
50 'Use wdesignkit/install-dependency to install missing free plugins from wordpress.org.',
51 'Pro/premium plugins (marked is_pro: true) must be installed manually.',
52 ]),
53 'readonly' => true,
54 'destructive' => false,
55 'idempotent' => true,
56 ],
57 ],
58 ]);
59
60 wp_register_ability('wdesignkit/install-dependency', [
61 'label' => __('Install Plugin Dependency', 'wdesignkit'),
62 'description' => __(
63 'Installs and activates a WordPress plugin from wordpress.org. Used to install required dependencies for WDesignKit widgets (e.g. Elementor, The Plus Addons).',
64 'wdesignkit',
65 ),
66 'category' => 'wdesignkit',
67 'input_schema' => [
68 'type' => 'object',
69 'properties' => [
70 'slug' => [
71 'type' => 'string',
72 'description' => 'Plugin slug on wordpress.org (e.g. "elementor", "the-plus-addons-for-elementor-page-builder").',
73 ],
74 'activate' => [
75 'type' => 'boolean',
76 'description' => 'Whether to activate the plugin after installing. Default true.',
77 ],
78 ],
79 'required' => ['slug'],
80 'additionalProperties' => false,
81 ],
82 'output_schema' => [
83 'type' => 'object',
84 'properties' => [
85 'success' => ['type' => 'boolean'],
86 'message' => ['type' => 'string'],
87 'is_pro' => ['type' => 'boolean'],
88 ],
89 ],
90 'execute_callback' => 'wdesignkit_mcp_install_dependency',
91 'permission_callback' => 'wdesignkit_mcp_permission_callback',
92 'meta' => [
93 // Installs plugin code from wordpress.org. The execute callback also checks this cap
94 // directly, which is what enforces it on WordPress < 7.1; declaring it here makes the
95 // requirement introspectable and enforces it before the callback runs on 7.1+.
96 'required_capability' => 'install_plugins',
97 'public' => true,
98 'show_in_rest' => true,
99 'mcp' => ['public' => true],
100 'annotations' => [
101 'instructions' => implode("\n", [
102 'Installs a WordPress plugin from wordpress.org repository.',
103 'Use wdesignkit/check-dependencies first to see what needs installing.',
104 'Only installs FREE plugins available on wordpress.org.',
105 'Installable slugs: elementor, the-plus-addons-for-elementor-page-builder, the-plus-addons-for-block-editor, nexter-extension.',
106 'Pro/premium slugs (theplus_elementor_addon, bricks) return success:false with is_pro:true — install those manually.',
107 'Do NOT attempt to install "wdesignkit" — it is already running.',
108 '"nexter" is a premium theme, not available on wordpress.org — install it manually.',
109 ]),
110 'readonly' => false,
111 'destructive' => false,
112 'idempotent' => true,
113 ],
114 ],
115 ]);
116
117 /**
118 * Canonical dependency manifest — single source of truth for both check-dependencies
119 * and install-dependency. Add or update entries here only; the callbacks derive their
120 * allowed/pro slug lists automatically.
121 *
122 * Entry keys:
123 * name, slug, required_for, optional, pro, minimum_version
124 * plugin_file — relative path used by is_plugin_active() (omit or '' for built-ins)
125 * builtin — true for WordPress core (always active, no install needed)
126 * theme — true for theme-based dependencies (checked via wp_get_themes())
127 */
128 function wdesignkit_mcp_dependency_manifest(): array {
129 return [
130 'elementor' => [
131 [
132 'name' => 'Elementor',
133 'slug' => 'elementor',
134 'plugin_file' => 'elementor/elementor.php',
135 'required_for' => 'elementor',
136 'optional' => false,
137 'pro' => false,
138 'minimum_version' => '3.0.0',
139 ],
140 [
141 'name' => 'The Plus Addons for Elementor (Free)',
142 'slug' => 'the-plus-addons-for-elementor-page-builder',
143 'plugin_file' => 'the-plus-addons-for-elementor-page-builder/theplus_elementor_addon.php',
144 'required_for' => 'elementor',
145 'optional' => true,
146 'pro' => false,
147 'minimum_version' => '5.0.0',
148 ],
149 [
150 'name' => 'The Plus Addons for Elementor (Pro)',
151 'slug' => 'theplus_elementor_addon',
152 'plugin_file' => 'theplus_elementor_addon/theplus_elementor_addon.php',
153 'required_for' => 'elementor',
154 'optional' => true,
155 'pro' => true,
156 'minimum_version' => '5.0.0',
157 ],
158 ],
159 'gutenberg' => [
160 [
161 'name' => 'The Plus Addons for Block Editor (Free)',
162 'slug' => 'the-plus-addons-for-block-editor',
163 'plugin_file' => 'the-plus-addons-for-block-editor/the-plus-addons-for-block-editor.php',
164 'required_for' => 'gutenberg',
165 'optional' => true,
166 'pro' => false,
167 'minimum_version' => '3.0.0',
168 ],
169 ],
170 'gutenberg_core' => [
171 // gutenberg_core uses native WordPress blocks — no extra dependencies required.
172 [
173 'name' => 'WordPress (Core Block Editor)',
174 'slug' => 'wordpress-core',
175 'plugin_file' => '',
176 'required_for' => 'gutenberg_core',
177 'optional' => false,
178 'pro' => false,
179 'builtin' => true,
180 'minimum_version' => '6.0.0',
181 ],
182 [
183 'name' => 'Nexter Extension',
184 'slug' => 'nexter-extension',
185 'plugin_file' => 'nexter-extension/nexter-extension.php',
186 'required_for' => 'gutenberg_core',
187 'optional' => true,
188 'pro' => false,
189 'minimum_version' => '2.0.0',
190 ],
191 ],
192 'bricks' => [
193 [
194 'name' => 'Bricks Builder',
195 'slug' => 'bricks',
196 'theme' => true,
197 'required_for' => 'bricks',
198 'optional' => false,
199 'pro' => true,
200 'minimum_version' => '1.5.0',
201 ],
202 ],
203 ];
204 }
205
206 function wdesignkit_mcp_check_dependencies(array $input): array {
207 // Guard against slow filesystem scans timing out on all-builders calls.
208 set_time_limit(60);
209
210 if (!function_exists('is_plugin_active')) {
211 include_once ABSPATH . 'wp-admin/includes/plugin.php';
212 }
213
214 $filter_builder = $input['builder'] ?? '';
215
216 // Load the shared manifest — single source of truth.
217 $all_deps = wdesignkit_mcp_dependency_manifest();
218
219 $deps_to_check = [];
220 if ($filter_builder !== '' && isset($all_deps[$filter_builder])) {
221 $deps_to_check = $all_deps[$filter_builder];
222 } else {
223 foreach ($all_deps as $builder_deps) {
224 $deps_to_check = array_merge($deps_to_check, $builder_deps);
225 }
226 }
227
228 $results = [];
229
230 // Hoist expensive lookups outside the loop — avoids repeated filesystem scans
231 // that were causing ~4 min timeouts on all-builders calls.
232 $all_plugins = get_plugins();
233 $all_themes = wp_get_themes();
234 $current_theme = get_stylesheet();
235 $parent_theme = get_template();
236
237 foreach ($deps_to_check as $dep) {
238 $min_version = $dep['minimum_version'] ?? '';
239
240 // Built-in (WordPress core) — always active.
241 if (!empty($dep['builtin'])) {
242 $wp_version = get_bloginfo('version');
243 $is_version_compatible = ($min_version === '') ? null : version_compare($wp_version, $min_version, '>=');
244 $results[] = [
245 'name' => $dep['name'],
246 'slug' => $dep['slug'],
247 'status' => 'active',
248 'version' => $wp_version,
249 'minimum_required_version' => $min_version,
250 'is_version_compatible' => $is_version_compatible,
251 'required_for' => $dep['required_for'],
252 'optional' => $dep['optional'] ?? false,
253 'is_pro' => $dep['pro'] ?? false,
254 'is_theme' => false,
255 ];
256 continue;
257 }
258
259 $status = 'not_installed';
260 $version = '';
261
262 if (!empty($dep['theme'])) {
263 // Check theme — uses pre-fetched $all_themes / $current_theme / $parent_theme.
264 if ($current_theme === $dep['slug'] || $parent_theme === $dep['slug']) {
265 $status = 'active';
266 $theme = $all_themes[$dep['slug']] ?? null;
267 $version = $theme ? ($theme->get('Version') ?: '') : '';
268 } elseif (isset($all_themes[$dep['slug']])) {
269 $status = 'installed_inactive';
270 $version = $all_themes[$dep['slug']]->get('Version') ?: '';
271 }
272 } else {
273 // Check plugin.
274 $plugin_file = $dep['plugin_file'] ?? '';
275 if ($plugin_file && is_plugin_active($plugin_file)) {
276 $status = 'active';
277 $version = $all_plugins[$plugin_file]['Version'] ?? '';
278 } elseif ($plugin_file && isset($all_plugins[$plugin_file])) {
279 $status = 'installed_inactive';
280 $version = $all_plugins[$plugin_file]['Version'] ?? '';
281 }
282 }
283
284 // is_version_compatible is null when not installed (no version to compare).
285 // When installed, compare against minimum_required_version if set.
286 $is_version_compatible = null;
287 if ($status !== 'not_installed' && $version !== '' && $min_version !== '') {
288 $is_version_compatible = version_compare($version, $min_version, '>=');
289 }
290
291 $results[] = [
292 'name' => $dep['name'],
293 'slug' => $dep['slug'],
294 'status' => $status,
295 'version' => $version,
296 'minimum_required_version' => $min_version,
297 'is_version_compatible' => $is_version_compatible,
298 'required_for' => $dep['required_for'],
299 'optional' => $dep['optional'] ?? false,
300 'is_pro' => $dep['pro'] ?? false,
301 'is_theme' => $dep['theme'] ?? false,
302 ];
303 }
304
305 return [
306 'success' => true,
307 'dependencies' => $results,
308 ];
309 }
310
311 function wdesignkit_mcp_install_dependency(array $input): array {
312 $slug = sanitize_text_field($input['slug'] ?? '');
313 $activate = $input['activate'] ?? true;
314
315 if (empty($slug)) {
316 return ['success' => false, 'message' => 'Plugin slug is required.'];
317 }
318
319 // Guard: self-installation is never valid — WDesignKit is already running.
320 if ($slug === 'wdesignkit') {
321 return [
322 'success' => false,
323 'message' => 'WDesignKit is already installed and running. Self-installation is not allowed.',
324 ];
325 }
326
327 // Derive pro slugs and installable slugs from the shared manifest — stays in sync automatically.
328 $manifest = wdesignkit_mcp_dependency_manifest();
329 $pro_slugs = [];
330 $allowed_slugs = [];
331
332 foreach ($manifest as $builder_deps) {
333 foreach ($builder_deps as $dep) {
334 // Built-ins (WordPress core) are never installed via this ability.
335 if (!empty($dep['builtin'])) {
336 continue;
337 }
338 $dep_slug = $dep['slug'] ?? '';
339 if ($dep_slug === '') {
340 continue;
341 }
342 if (!empty($dep['pro'])) {
343 // Pro/premium — visible in check-dependencies but not installable from wp.org.
344 $pro_slugs[] = $dep_slug;
345 } elseif (empty($dep['theme']) && !empty($dep['plugin_file'])) {
346 // Free plugins with a known plugin_file are installable from wordpress.org.
347 $allowed_slugs[] = $dep_slug;
348 }
349 }
350 }
351
352 // Nexter theme is a known pro product that callers occasionally attempt to install.
353 // It is not in the manifest (not a builder dependency) but deserves a helpful error.
354 $pro_slugs[] = 'nexter';
355
356 $pro_slugs = array_unique($pro_slugs);
357 $allowed_slugs = array_unique($allowed_slugs);
358
359 if (in_array($slug, $pro_slugs, true)) {
360 return [
361 'success' => false,
362 'message' => "'{$slug}' is a premium product and cannot be installed from wordpress.org. Please purchase and install it manually.",
363 'is_pro' => true,
364 ];
365 }
366
367 if (!in_array($slug, $allowed_slugs, true)) {
368 return [
369 'success' => false,
370 'message' => "Plugin '{$slug}' is not a recognised WDesignKit dependency. Use wdesignkit/check-dependencies to see installable slugs.",
371 ];
372 }
373
374 if (!current_user_can('install_plugins')) {
375 return ['success' => false, 'message' => 'Insufficient permissions to install plugins.'];
376 }
377
378 include_once ABSPATH . 'wp-admin/includes/plugin.php';
379 include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
380 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
381 include_once ABSPATH . 'wp-admin/includes/file.php';
382
383 // Check if already installed
384 $all_plugins = get_plugins();
385 $plugin_file = '';
386
387 foreach ($all_plugins as $file => $data) {
388 $file_slug = dirname($file);
389 if ($file_slug === '.') {
390 $file_slug = pathinfo($file, PATHINFO_FILENAME);
391 }
392 if ($file_slug === $slug) {
393 $plugin_file = $file;
394 break;
395 }
396 }
397
398 if ($plugin_file) {
399 if ($activate && !is_plugin_active($plugin_file)) {
400 $result = activate_plugin($plugin_file);
401 if (is_wp_error($result)) {
402 return [
403 'success' => false,
404 'message' => 'Plugin is installed but activation failed: ' . $result->get_error_message(),
405 ];
406 }
407 return [
408 'success' => true,
409 'message' => "Plugin '{$slug}' was already installed. Activated successfully.",
410 ];
411 }
412
413 return [
414 'success' => true,
415 'message' => "Plugin '{$slug}' is already installed" . (is_plugin_active($plugin_file) ? ' and active.' : '. Use activate parameter to activate it.'),
416 ];
417 }
418
419 // Get plugin info from wordpress.org
420 $api = plugins_api('plugin_information', [
421 'slug' => $slug,
422 'fields' => ['sections' => false],
423 ]);
424
425 if (is_wp_error($api)) {
426 return [
427 'success' => false,
428 'message' => "Plugin '{$slug}' not found on wordpress.org: " . $api->get_error_message(),
429 ];
430 }
431
432 // Install
433 $upgrader = new Plugin_Upgrader(new Automatic_Upgrader_Skin());
434 $result = $upgrader->install($api->download_link);
435
436 if (is_wp_error($result)) {
437 return [
438 'success' => false,
439 'message' => 'Installation failed: ' . $result->get_error_message(),
440 ];
441 }
442
443 if (!$result) {
444 return [
445 'success' => false,
446 'message' => 'Installation failed. Please try manually from WP Admin → Plugins → Add New.',
447 ];
448 }
449
450 // Activate if requested
451 if ($activate) {
452 $all_plugins = get_plugins();
453 $plugin_file = '';
454
455 foreach ($all_plugins as $file => $data) {
456 $file_slug = dirname($file);
457 if ($file_slug === '.') {
458 $file_slug = pathinfo($file, PATHINFO_FILENAME);
459 }
460 if ($file_slug === $slug) {
461 $plugin_file = $file;
462 break;
463 }
464 }
465
466 if ($plugin_file) {
467 $activate_result = activate_plugin($plugin_file);
468 if (is_wp_error($activate_result)) {
469 return [
470 'success' => true,
471 'message' => "Plugin '{$slug}' installed but activation failed: " . $activate_result->get_error_message(),
472 ];
473 }
474
475 return [
476 'success' => true,
477 'message' => "Plugin '{$slug}' installed and activated successfully.",
478 ];
479 }
480 }
481
482 return [
483 'success' => true,
484 'message' => "Plugin '{$slug}' installed successfully.",
485 ];
486 }
487