PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.1
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.1
3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 2.8.17 2.8.18 All 259 releases
wpfunnels / includes / core / MCP / Tools / SetupWizardTools.php

SetupWizardTools.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.1, at includes/core/MCP/Tools/SetupWizardTools.php

291 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * SetupWizardTools — setup wizard plugin activation and add-on discovery.
4 *
5 * Wraps the setup wizard's AJAX-only plugin activation handlers
6 * (admin/modules/setup-wizard/setup-wizard.php) as callable abilities, plus a
7 * read-only listing of the recommended add-ons shown on the Add-ons admin
8 * page (admin/modules/addons/view.php).
9 *
10 * `ajax_update_step_meta()` (also defined in setup-wizard.php) is deliberately
11 * NOT wrapped here: it has no allow-list of meta keys today (any key the
12 * caller names is written via `update_post_meta()`), and a repo-wide search
13 * for its only registered AJAX action, `wp_ajax_wpfnl_update_step_meta`,
14 * turned up no caller anywhere in this plugin's or WPFunnels Pro's JS/Vue —
15 * it appears to be dead code. Wrapping it would mean guessing at an
16 * allow-list rather than confirming real meta keys, which the security
17 * review for this tool set explicitly rules out.
18 *
19 * @package WPFunnels\MCP
20 * @since 3.14.0
21 */
22
23 namespace WPFunnels\MCP\Tools;
24
25 defined( 'ABSPATH' ) || exit;
26
27 use WPFunnels\MCP\Helpers\MCPHelper;
28
29 /**
30 * Class SetupWizardTools
31 */
32 class SetupWizardTools {
33
34 /**
35 * Ability definitions for this domain.
36 *
37 * @return array
38 */
39 public static function definitions() {
40 return [
41 'wpfunnels/activate-plugin' => [
42 'label' => __( 'Activate Plugin', 'wpfnl' ),
43 'description' => 'Activate a single already-installed plugin by its file path (e.g. "woocommerce/woocommerce.php"), the same action the setup wizard\'s per-plugin "Activate" buttons perform. The plugin must already be installed — this never downloads or installs anything.',
44 'input_schema' => [
45 'type' => 'object',
46 'properties' => [
47 'plugin' => [
48 'type' => 'string',
49 'description' => 'Plugin file path relative to the plugins directory, e.g. "woocommerce/woocommerce.php" or "elementor/elementor.php".',
50 ],
51 ],
52 'required' => [ 'plugin' ],
53 ],
54 'execute_callback' => [ __CLASS__, 'activatePlugin' ],
55 'permission_callback' => MCPHelper::currentUserCan( 'activate_plugins' ),
56 'annotations' => [ 'destructive' ],
57 ],
58 'wpfunnels/activate-mail-mint' => [
59 'label' => __( 'Activate Mail Mint', 'wpfnl' ),
60 'description' => 'Activate the Mail Mint plugin and run its first-run database setup, the same action the setup wizard\'s Mail Mint step performs. Mail Mint must already be installed.',
61 'input_schema' => [
62 'type' => 'object',
63 'properties' => [],
64 ],
65 'execute_callback' => [ __CLASS__, 'activateMailMint' ],
66 'permission_callback' => MCPHelper::currentUserCan( 'activate_plugins' ),
67 'annotations' => [ 'destructive' ],
68 ],
69 'wpfunnels/list-recommended-addons' => [
70 'label' => __( 'List Recommended Add-ons', 'wpfnl' ),
71 'description' => 'The add-ons WPFunnels recommends on its Add-ons admin page, each with its current install/active status on this site.',
72 'input_schema' => [
73 'type' => 'object',
74 'properties' => [],
75 ],
76 'execute_callback' => [ __CLASS__, 'listRecommendedAddons' ],
77 'permission_callback' => MCPHelper::currentUserCan(),
78 'annotations' => [ 'readonly' ],
79 ],
80 ];
81 }
82
83 /**
84 * Activate a single plugin by file path.
85 *
86 * Reimplements the body of
87 * \WPFunnels\Admin\SetupWizard::ajax_activate_plugin_static() as a
88 * plain-argument callable — that method reads `$_POST['plugin']` directly
89 * and calls `wp_send_json_*()`/`wp_die()`, so it cannot be called as-is
90 * outside of an AJAX request.
91 *
92 * (Also considered reusing \WPFunnels\Admin\SetupWizard\ActivatePlugin,
93 * the bulk-activation helper behind the REST `activate_plugins` endpoint —
94 * its `activate_plugins()` expects an array of
95 * `{name, status, path, type, slug}` descriptors taken straight from
96 * `SetupWizard::get_essential_plugins()`, has no `file_exists()`/
97 * `is_plugin_active()` guards, and can't be driven cleanly from a single
98 * plugin-file-path argument, so this method mirrors the simpler AJAX
99 * handler instead of forcing that shape.)
100 *
101 * @param array $input Tool input.
102 * @return array|\WP_Error
103 */
104 public static function activatePlugin( $input = [] ) {
105 $plugin = isset( $input['plugin'] ) ? sanitize_text_field( $input['plugin'] ) : '';
106
107 if ( '' === $plugin ) {
108 return MCPHelper::error( 'missing_plugin', 'Provide the plugin file path, e.g. "woocommerce/woocommerce.php".' );
109 }
110
111 if ( ! function_exists( 'is_plugin_active' ) || ! function_exists( 'activate_plugin' ) ) {
112 require_once ABSPATH . 'wp-admin/includes/plugin.php';
113 }
114
115 $plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
116 if ( ! file_exists( $plugin_file ) ) {
117 return MCPHelper::error(
118 'plugin_not_found',
119 sprintf( 'Plugin file not found: %s. It must be installed before it can be activated.', $plugin )
120 );
121 }
122
123 if ( is_plugin_active( $plugin ) ) {
124 return [
125 'success' => true,
126 'plugin' => $plugin,
127 'message' => 'Plugin is already active.',
128 ];
129 }
130
131 $result = activate_plugin( $plugin, '', false, true );
132 if ( is_wp_error( $result ) ) {
133 return $result;
134 }
135
136 // Special handling for WooCommerce, mirroring the AJAX handler.
137 if ( false !== strpos( $plugin, 'woocommerce' ) ) {
138 delete_transient( '_wc_activation_redirect' );
139 }
140
141 // Special handling for Elementor, mirroring the AJAX handler.
142 if ( false !== strpos( $plugin, 'elementor' ) ) {
143 update_option( 'elementor_onboarded', true );
144 }
145
146 return [
147 'success' => true,
148 'plugin' => $plugin,
149 'message' => 'Plugin activated successfully.',
150 ];
151 }
152
153 /**
154 * Activate Mail Mint and run its first-run database setup.
155 *
156 * Reimplements the body of
157 * \WPFunnels\Admin\SetupWizard::ajax_activate_mail_mint() as a
158 * plain-argument callable, for the same reason as activatePlugin() above.
159 *
160 * @param array $input Tool input.
161 * @return array|\WP_Error
162 */
163 public static function activateMailMint( $input = [] ) {
164 if ( ! function_exists( 'is_plugin_active' ) || ! function_exists( 'activate_plugin' ) ) {
165 require_once ABSPATH . 'wp-admin/includes/plugin.php';
166 }
167
168 $mail_mint_plugin = 'mail-mint/mail-mint.php';
169 $mail_mint_file = WP_PLUGIN_DIR . '/' . $mail_mint_plugin;
170
171 if ( ! is_plugin_active( $mail_mint_plugin ) ) {
172 if ( ! file_exists( $mail_mint_file ) ) {
173 return MCPHelper::error( 'plugin_not_found', 'Mail Mint is not installed. It must be installed before it can be activated.' );
174 }
175
176 $result = activate_plugin( $mail_mint_plugin, '', false, true );
177 if ( is_wp_error( $result ) ) {
178 return $result;
179 }
180 }
181
182 // Explicitly trigger Mail Mint's activation logic so its databases
183 // exist even when core `activate_plugin()` alone didn't run it (same
184 // PHP 7.4 safety net as the original AJAX handler).
185 if ( file_exists( $mail_mint_file ) ) {
186 $activator_file = WP_PLUGIN_DIR . '/mail-mint/includes/MrmActivator.php';
187 if ( file_exists( $activator_file ) ) {
188 require_once $activator_file;
189 if ( class_exists( '\MrmActivator' ) ) {
190 \MrmActivator::activate();
191 }
192 }
193
194 delete_transient( 'mailmint_show_setup_wizard' );
195 }
196
197 return [
198 'success' => true,
199 'message' => 'Mail Mint activated and initialized successfully.',
200 ];
201 }
202
203 /**
204 * The recommended add-ons shown on the Add-ons admin page, with
205 * install/active status computed the same way that page computes it.
206 *
207 * Mirrors the `$addons` array built inline in
208 * admin/modules/addons/view.php (including the `wpfnl_recommended_addons`
209 * filter it runs through), minus the inline SVG icon markup, which has no
210 * use for an AI agent.
211 *
212 * @param array $input Tool input.
213 * @return array
214 */
215 public static function listRecommendedAddons( $input = [] ) {
216 if ( ! function_exists( 'is_plugin_active' ) ) {
217 require_once ABSPATH . 'wp-admin/includes/plugin.php';
218 }
219
220 $addons = [
221 [
222 'title' => __( 'Integrations', 'wpfnl' ),
223 'description' => __( 'Connect WPFunnels with marketing tools, automate customer journey, sync contacts, and trigger campaigns — all without leaving your funnel.', 'wpfnl' ),
224 'guide_link' => 'https://getwpfunnels.com/docs/funnel-integrations/',
225 'cta_link' => 'https://getwpfunnels.com/pricing/',
226 'plugin_file' => 'wpfunnels-pro-integrations/wpfunnels-pro-integrations.php',
227 ],
228 [
229 'title' => __( 'Storewide Checkout (Global Funnel)', 'wpfnl' ),
230 'description' => __( 'Apply a single funnel flow across your entire store and replace the default WooCommerce checkout - no need to create funnels individually.', 'wpfnl' ),
231 'guide_link' => 'https://getwpfunnels.com/docs/global-funnels-for-woocommerce/',
232 'cta_link' => 'https://getwpfunnels.com/pricing/',
233 'plugin_file' => 'wpfunnels-pro/wpfnl-pro.php',
234 ],
235 [
236 'title' => __( 'LMS', 'wpfnl' ),
237 'description' => __( 'Seamlessly sell and deliver online courses, manage students, and automate access after purchase — all inside your funnel ecosystem.', 'wpfnl' ),
238 'guide_link' => 'https://getwpfunnels.com/docs/sales-funnels-for-courses/',
239 'cta_link' => 'https://getwpfunnels.com/pricing/',
240 'plugin_file' => 'wpfunnels-pro-lms/wpfunnels-pro-lms.php',
241 ],
242 [
243 'title' => __( 'Checkoutify', 'wpfnl' ),
244 'description' => __( 'Use the most unique checkout field editor for WooCommerce to design and optimize your checkout form with full control.', 'wpfnl' ),
245 'guide_link' => 'https://getwpfunnels.com/docs/checkoutify-documentations-guides/',
246 'cta_link' => 'https://getwpfunnels.com/pricing/',
247 'plugin_file' => 'checkoutify/checkoutify.php',
248 ],
249 ];
250
251 /**
252 * Filters the recommended add-ons list.
253 *
254 * Same filter admin/modules/addons/view.php runs its own copy of this
255 * array through, so anything a third party (or WPFunnels Pro) adds to
256 * the admin page shows up here too.
257 *
258 * @since 3.14.0
259 * @param array $addons Recommended add-ons.
260 */
261 $addons = apply_filters( 'wpfnl_recommended_addons', $addons );
262
263 $items = [];
264 foreach ( (array) $addons as $addon ) {
265 $plugin_file = isset( $addon['plugin_file'] ) ? $addon['plugin_file'] : '';
266 $is_installed = false;
267 $is_active = false;
268
269 if ( '' !== $plugin_file ) {
270 $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $plugin_file );
271 $is_active = is_plugin_active( $plugin_file );
272 }
273
274 $items[] = [
275 'title' => isset( $addon['title'] ) ? $addon['title'] : '',
276 'description' => isset( $addon['description'] ) ? $addon['description'] : '',
277 'plugin_file' => $plugin_file,
278 'guide_link' => isset( $addon['guide_link'] ) ? $addon['guide_link'] : '',
279 'cta_link' => isset( $addon['cta_link'] ) ? $addon['cta_link'] : '',
280 'is_installed' => $is_installed,
281 'is_active' => $is_active,
282 ];
283 }
284
285 return [
286 'addons' => $items,
287 'addon_count' => count( $items ),
288 ];
289 }
290 }
291