PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.2
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.2
3.13.3 3.13.2 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 All 261 releases
wpfunnels / includes / core / rest-api / Controllers / class-settings-page-controller.php

class-settings-page-controller.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.2, at includes/core/rest-api/Controllers/class-settings-page-controller.php

538 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST controller for the settings screen (`?page=wpfunnels#/settings`).
4 *
5 * The screen is React now, and everything it reads or writes goes through here.
6 * Nothing on the server moved: saving hands the same payload the legacy
7 * `update-general-settings` admin-ajax action received to the very same handler
8 * (Settings Module::update_general_settings), so every hook that listens on
9 * `wpfunnels/before_settings_saved` and `wpfunnels/after_settings_saved` — the
10 * Pro offer, GTM, Facebook Pixel and UTM writers among them — still fires
11 * exactly as before. The admin-ajax actions stay registered too, so anything
12 * else that posts to them keeps working.
13 *
14 * @package WPFunnels\Rest\Controllers
15 */
16
17 namespace WPFunnels\Rest\Controllers;
18
19 use WP_Error;
20 use WP_REST_Request;
21 use WP_REST_Response;
22 use WP_REST_Server;
23 use WPFunnels\Wpfnl_functions;
24
25 if ( ! defined( 'ABSPATH' ) ) {
26 exit;
27 }
28
29 /**
30 * Class SettingsPageController
31 *
32 * @package WPFunnels\Rest\Controllers
33 */
34 class SettingsPageController extends Wpfnl_REST_Controller {
35
36 /**
37 * Endpoint namespace.
38 *
39 * @var string
40 */
41 protected $namespace = 'wpfunnels/v1';
42
43 /**
44 * Route base.
45 *
46 * @var string
47 */
48 protected $rest_base = 'settings/page';
49
50
51 /**
52 * Whether the current user may read the settings screen.
53 *
54 * @return bool|WP_Error
55 * @since 3.13.8
56 */
57 public function get_items_permissions_check( $request ) {
58 if ( ! Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'settings' ) ) {
59 return new WP_Error(
60 'wpfunnels_rest_cannot_view',
61 __( 'Sorry, you cannot list resources.', 'wpfnl' ),
62 array( 'status' => rest_authorization_required_code() )
63 );
64 }
65
66 return true;
67 }
68
69
70 /**
71 * Whether the current user may save the settings screen.
72 *
73 * @return bool|WP_Error
74 * @since 3.13.8
75 */
76 public function update_items_permissions_check( $request ) {
77 if ( ! Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'settings' ) ) {
78 return new WP_Error(
79 'wpfunnels_rest_cannot_edit',
80 __( 'Sorry, you cannot edit this resource.', 'wpfnl' ),
81 array( 'status' => rest_authorization_required_code() )
82 );
83 }
84
85 return true;
86 }
87
88
89 /**
90 * Register the routes the settings screen talks to.
91 *
92 * @return void
93 * @since 3.13.8
94 */
95 public function register_routes() {
96 register_rest_route(
97 $this->namespace,
98 '/' . $this->rest_base,
99 array(
100 array(
101 'methods' => WP_REST_Server::READABLE,
102 'callback' => array( $this, 'get_settings' ),
103 'permission_callback' => array( $this, 'get_items_permissions_check' ),
104 ),
105 array(
106 'methods' => WP_REST_Server::CREATABLE,
107 'callback' => array( $this, 'save_settings' ),
108 'permission_callback' => array( $this, 'update_items_permissions_check' ),
109 ),
110 )
111 );
112
113 register_rest_route(
114 $this->namespace,
115 '/settings/clear-templates',
116 array(
117 array(
118 'methods' => WP_REST_Server::CREATABLE,
119 'callback' => array( $this, 'clear_templates' ),
120 'permission_callback' => array( $this, 'update_items_permissions_check' ),
121 ),
122 )
123 );
124
125 register_rest_route(
126 $this->namespace,
127 '/settings/clear-transients',
128 array(
129 array(
130 'methods' => WP_REST_Server::CREATABLE,
131 'callback' => array( $this, 'clear_transients' ),
132 'permission_callback' => array( $this, 'update_items_permissions_check' ),
133 ),
134 )
135 );
136
137 register_rest_route(
138 $this->namespace,
139 '/settings/logs',
140 array(
141 array(
142 'methods' => WP_REST_Server::READABLE,
143 'callback' => array( $this, 'get_log' ),
144 'permission_callback' => array( $this, 'get_items_permissions_check' ),
145 'args' => array(
146 'file' => array(
147 'description' => __( 'Log file name.', 'wpfnl' ),
148 'type' => 'string',
149 ),
150 ),
151 ),
152 array(
153 'methods' => WP_REST_Server::DELETABLE,
154 'callback' => array( $this, 'delete_log' ),
155 'permission_callback' => array( $this, 'update_items_permissions_check' ),
156 'args' => array(
157 'file' => array(
158 'description' => __( 'Log file name.', 'wpfnl' ),
159 'type' => 'string',
160 ),
161 ),
162 ),
163 )
164 );
165
166 register_rest_route(
167 $this->namespace,
168 '/settings/test-notification',
169 array(
170 array(
171 'methods' => WP_REST_Server::CREATABLE,
172 'callback' => array( $this, 'send_test_notification' ),
173 'permission_callback' => array( $this, 'update_items_permissions_check' ),
174 ),
175 )
176 );
177 }
178
179
180 /**
181 * The settings module, which still owns every write this screen makes.
182 *
183 * @return \WPFunnels\Modules\Admin\Settings\Module|null
184 * @since 3.13.8
185 */
186 private function get_settings_module() {
187 if ( ! class_exists( '\WPFunnels\Modules\Admin\Settings\Module' ) ) {
188 return null;
189 }
190
191 return \WPFunnels\Modules\Admin\Settings\Module::instance();
192 }
193
194
195 /**
196 * Everything the settings screen renders from.
197 *
198 * One request rather than one per panel: the screen saves every panel at
199 * once, so it has to hold every panel's values anyway — a field the reader
200 * never opened still has to be sent back untouched.
201 *
202 * @param WP_REST_Request $request Request.
203 *
204 * @return WP_REST_Response
205 * @since 3.13.8
206 */
207 public function get_settings( $request ) {
208 $module = $this->get_settings_module();
209
210 $general = Wpfnl_functions::get_general_settings();
211 $permalink = Wpfnl_functions::get_permalink_settings();
212 $optin = Wpfnl_functions::get_optin_settings();
213 $offer = Wpfnl_functions::get_offer_settings();
214 $recaptcha = Wpfnl_functions::get_recaptcha_settings();
215 $gtm = Wpfnl_functions::get_gtm_settings();
216 $pixel = Wpfnl_functions::get_facebook_pixel_settings();
217
218 $is_pro_activated = Wpfnl_functions::is_wpfnl_pro_activated();
219 $is_pro_license_active = (bool) apply_filters( 'wpfunnels/is_pro_license_activated', false );
220
221 return rest_ensure_response(
222 array(
223 'success' => true,
224 'general' => $general,
225 'permalink' => $permalink,
226 'optin' => $optin,
227 'offer' => $offer,
228 'recaptcha' => $recaptcha,
229 'gtm' => $gtm,
230 'pixel' => $pixel,
231 'notification' => Wpfnl_functions::get_notification_settings(),
232 'userRoles' => Wpfnl_functions::get_user_role_settings(),
233 'googleMapKey' => Wpfnl_functions::get_google_map_api_key(),
234 'lms' => $this->get_lms_data(),
235 'options' => array(
236 'builders' => Wpfnl_functions::get_supported_builders(),
237 'canvasMode' => Wpfnl_functions::get_default_funnel_builder_mode(),
238 'gtmEvents' => Wpfnl_functions::get_gtm_events(),
239 'pixelEvents' => Wpfnl_functions::get_facebook_events(),
240 'analyticsRoles' => array_values( Wpfnl_functions::get_user_roles() ),
241 'rollbackVersions' => $module ? array_values( (array) $module->get_roll_back_versions() ) : array(),
242 /*
243 * Reinstalling is an admin-post action, not a setting, so the
244 * screen follows this URL with the chosen version swapped in
245 * for the VERSION placeholder — the nonce has to be minted
246 * here, which is exactly what the legacy view did.
247 */
248 'rollbackUrl' => wp_nonce_url( admin_url( 'admin-post.php?action=wpfunnels_rollback&version=VERSION' ), 'wpfunnels_rollback' ),
249 'logFiles' => $this->get_log_files(),
250 'homeUrl' => home_url(),
251 'adminEmail' => get_option( 'admin_email' ),
252 'version' => WPFNL_VERSION,
253 'imagePath' => WPFNL_URL . 'admin/assets/images',
254 'defaultStepBase' => WPFNL_STEPS_POST_TYPE,
255 'defaultFunnelBase' => WPFNL_FUNNELS_POST_TYPE,
256 'proUrl' => add_query_arg( 'wpfnl-dashboard', '1', GETWPFUNNEL_PRICING_URL ),
257 'pricingUrl' => GETWPFUNNEL_PRICING_URL,
258 ),
259 'capabilities' => array(
260 'isWcActive' => Wpfnl_functions::is_wc_active(),
261 'isLmsAddonActive' => Wpfnl_functions::is_lms_addon_active(),
262 'isProActivated' => (bool) $is_pro_activated,
263 'isProLicenseActive' => $is_pro_license_active,
264 'isGlobalFunnel' => (bool) Wpfnl_functions::is_global_funnel_activated(),
265 'canManageOptions' => current_user_can( 'manage_options' ),
266 // The analytics panel is behind both the Pro plugin running
267 // and the same filter the legacy view checked.
268 'isAnalyticsVisible' => (bool) apply_filters( 'wpfunnels/is_wpfnl_pro_active', false ),
269 'trackingConsent' => $this->get_tracking_consent(),
270 ),
271 )
272 );
273 }
274
275
276 /**
277 * Save the whole screen.
278 *
279 * The payload is handed to the settings module unchanged — same keys, same
280 * handler, same hooks — so this route is a transport change and nothing
281 * more.
282 *
283 * @param WP_REST_Request $request Request.
284 *
285 * @return WP_REST_Response|WP_Error
286 * @since 3.13.8
287 */
288 public function save_settings( $request ) {
289 $module = $this->get_settings_module();
290
291 if ( ! $module ) {
292 return new WP_Error(
293 'wpfunnels_settings_unavailable',
294 __( 'Settings module is not available.', 'wpfnl' ),
295 array( 'status' => 500 )
296 );
297 }
298
299 $payload = $request->get_json_params();
300
301 if ( empty( $payload ) || ! is_array( $payload ) ) {
302 $payload = $request->get_params();
303 }
304
305 // `builder` is read unguarded by the handler, and an empty one would
306 // wipe the site's page builder — fall back to what is stored.
307 if ( empty( $payload['builder'] ) ) {
308 $general = Wpfnl_functions::get_general_settings();
309 $payload['builder'] = isset( $general['builder'] ) ? $general['builder'] : 'gutenberg';
310 }
311
312 $module->update_general_settings( $payload );
313
314 /*
315 * Permalink changes only take effect once the rewrite rules are flushed,
316 * which admin_init does on the next page load (see
317 * Module::after_permalink_settings_saved). The React screen never
318 * reloads, so flush here as well — the option the handler set is what
319 * both paths key on, and deleting it keeps the admin_init pass a no-op.
320 */
321 if ( get_option( '_wpfunnels_permalink_saved' ) ) {
322 flush_rewrite_rules();
323 delete_option( '_wpfunnels_permalink_saved' );
324 }
325
326 $response = $this->get_settings( $request );
327 $data = $response->get_data();
328 $data['saved'] = true;
329
330 return rest_ensure_response( $data );
331 }
332
333
334 /**
335 * Drop the cached template listings so the next one is fetched fresh.
336 *
337 * @param WP_REST_Request $request Request.
338 *
339 * @return WP_REST_Response|WP_Error
340 * @since 3.13.8
341 */
342 public function clear_templates( $request ) {
343 $module = $this->get_settings_module();
344
345 if ( ! $module ) {
346 return new WP_Error(
347 'wpfunnels_settings_unavailable',
348 __( 'Settings module is not available.', 'wpfnl' ),
349 array( 'status' => 500 )
350 );
351 }
352
353 return rest_ensure_response( $module->clear_templates_data( array() ) );
354 }
355
356
357 /**
358 * Clear every transient WPFunnels owns.
359 *
360 * @param WP_REST_Request $request Request.
361 *
362 * @return WP_REST_Response|WP_Error
363 * @since 3.13.8
364 */
365 public function clear_transients( $request ) {
366 $module = $this->get_settings_module();
367
368 if ( ! $module ) {
369 return new WP_Error(
370 'wpfunnels_settings_unavailable',
371 __( 'Settings module is not available.', 'wpfnl' ),
372 array( 'status' => 500 )
373 );
374 }
375
376 return rest_ensure_response( $module->clear_transient_cache_data( array() ) );
377 }
378
379
380 /**
381 * Read one log file.
382 *
383 * @param WP_REST_Request $request Request.
384 *
385 * @return WP_REST_Response
386 * @since 3.13.8
387 */
388 public function get_log( $request ) {
389 $file = $request->get_param( 'file' );
390
391 if ( empty( $file ) ) {
392 return rest_ensure_response(
393 array(
394 'success' => true,
395 'files' => $this->get_log_files(),
396 'content' => '',
397 )
398 );
399 }
400
401 $result = \WPFunnels\Modules\Admin\Settings\Module::wpfnl_show_log( array( 'logKey' => $file ) );
402 $result['files'] = $this->get_log_files();
403
404 return rest_ensure_response( $result );
405 }
406
407
408 /**
409 * Delete one log file.
410 *
411 * @param WP_REST_Request $request Request.
412 *
413 * @return WP_REST_Response
414 * @since 3.13.8
415 */
416 public function delete_log( $request ) {
417 $file = $request->get_param( 'file' );
418
419 $result = \WPFunnels\Modules\Admin\Settings\Module::wpfnl_delete_log( array( 'logKey' => $file ) );
420 $result['files'] = $this->get_log_files();
421
422 return rest_ensure_response( $result );
423 }
424
425
426 /**
427 * Send the revenue report to one address, as a test.
428 *
429 * @param WP_REST_Request $request Request.
430 *
431 * @return WP_REST_Response|WP_Error
432 * @since 3.13.8
433 */
434 public function send_test_notification( $request ) {
435 $module = $this->get_settings_module();
436
437 if ( ! $module ) {
438 return new WP_Error(
439 'wpfunnels_settings_unavailable',
440 __( 'Settings module is not available.', 'wpfnl' ),
441 array( 'status' => 500 )
442 );
443 }
444
445 $payload = $request->get_json_params();
446
447 if ( empty( $payload ) || ! is_array( $payload ) ) {
448 $payload = $request->get_params();
449 }
450
451 $email = isset( $payload['email'] ) ? sanitize_email( $payload['email'] ) : '';
452
453 return rest_ensure_response( $module->send_test_notification( array( 'email' => $email ) ) );
454 }
455
456
457 /**
458 * Log files, as the screen's select renders them.
459 *
460 * The value stays the real file name; the label drops the site-specific
461 * suffix, exactly as the screen this replaced did.
462 *
463 * @return array List of `{ value, label }`.
464 * @since 3.13.8
465 */
466 private function get_log_files() {
467 if ( ! class_exists( '\Wpfnl_Logger' ) ) {
468 return array();
469 }
470
471 $files = \Wpfnl_Logger::get_log_files();
472 $suffix = \Wpfnl_Logger::get_log_file_suffix();
473 $result = array();
474
475 foreach ( (array) $files as $file ) {
476 $result[] = array(
477 'value' => $file,
478 'label' => str_replace( '-' . $suffix, '', $file ),
479 );
480 }
481
482 return $result;
483 }
484
485
486 /**
487 * Available LMS providers and the one in use.
488 *
489 * @return array
490 * @since 3.13.8
491 */
492 private function get_lms_data() {
493 $providers = array();
494 $selected = '';
495
496 if ( class_exists( '\WPFunnels\lms\settings\LMS_Settings_Helper' ) ) {
497 $providers = \WPFunnels\lms\settings\LMS_Settings_Helper::get_available_lms_providers();
498 $selected = \WPFunnels\lms\settings\LMS_Settings_Helper::get_selected_lms_provider();
499 }
500
501 $list = array();
502
503 foreach ( (array) $providers as $id => $provider ) {
504 $list[] = array(
505 'value' => $id,
506 'label' => isset( $provider['name'] ) ? $provider['name'] : $id,
507 );
508 }
509
510 return array(
511 'providers' => $list,
512 'selected' => $selected,
513 );
514 }
515
516
517 /**
518 * Usage-tracking consent as the Advanced panel renders it.
519 *
520 * A site that has never answered the setup-wizard question reads as 'no',
521 * which is also what the legacy view rendered — the difference between
522 * "undecided" and "opted out" is kept by the screen only sending the toggle
523 * when it actually changed.
524 *
525 * @return string 'yes' or 'no'.
526 * @since 3.13.8
527 */
528 private function get_tracking_consent() {
529 if ( ! class_exists( '\WPFunnels\Tracking\Telemetry' ) ) {
530 return 'no';
531 }
532
533 $telemetry = \WPFunnels\Tracking\Telemetry::instance();
534
535 return ( $telemetry && 'yes' === $telemetry->get_consent_state() ) ? 'yes' : 'no';
536 }
537 }
538