PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / admin-core / ajax / common-settings.php

common-settings.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at admin-core/ajax/common-settings.php

681 lines 21.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CartFlows Flows ajax actions.
4 *
5 * @package CartFlows
6 */
7
8 namespace CartflowsAdmin\AdminCore\Ajax;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 use CartflowsAdmin\AdminCore\Ajax\AjaxBase;
16 use CartflowsAdmin\AdminCore\Inc\AdminHelper;
17
18 /**
19 * Class Flows.
20 */
21 class CommonSettings extends AjaxBase {
22
23 /**
24 * Instance
25 *
26 * @access private
27 * @var object Class object.
28 * @since 1.0.0
29 */
30 private static $instance;
31
32 /**
33 * URL the client should hard-navigate to after a successful save.
34 *
35 * Set by save_other_settings() when the legacy-admin toggle is freshly
36 * enabled so the dashboard reloads through the legacy loader. Read by
37 * save_global_settings() and merged into the final response payload so
38 * do_action() hooks and other side-effects still run before the redirect.
39 *
40 * @var string
41 */
42 private $pending_redirect = '';
43
44 /**
45 * Fields the save skipped because the user lacks `unfiltered_html`.
46 *
47 * @var array<int, string>
48 */
49 private $skipped_fields = array();
50
51 /**
52 * Initiator
53 *
54 * @since 1.0.0
55 * @return object initialized object of class.
56 */
57 public static function get_instance() {
58 if ( ! isset( self::$instance ) ) {
59 self::$instance = new self();
60 }
61 return self::$instance;
62 }
63
64 /**
65 * Register_ajax_events.
66 *
67 * @return void
68 */
69 public function register_ajax_events() {
70
71 if ( current_user_can( 'cartflows_manage_settings' ) ) {
72
73 $ajax_events = array(
74 'save_global_settings',
75 'regenerate_css_for_steps',
76 'track_kb_search',
77 'migrate_custom_scripts',
78 );
79 $this->init_ajax_events( $ajax_events );
80 }
81 }
82
83 /**
84 * Delete the post meta key for dynamic css to regenerate the it.
85 */
86 public function regenerate_css_for_steps() {
87
88 $response_data = array( 'messsage' => $this->get_error_msg( 'permission' ) );
89
90 if ( ! current_user_can( 'cartflows_manage_settings' ) ) {
91 wp_send_json_error( $response_data );
92 }
93
94 /**
95 * Nonce verification
96 */
97 if ( ! check_ajax_referer( 'cartflows_regenerate_css_for_steps', 'security', false ) ) {
98 $response_data = array( 'messsage' => $this->get_error_msg( 'nonce' ) );
99 wp_send_json_error( $response_data );
100 }
101
102 // Update cartflows asset version to regenerate the dynamic css. We are using the time() function to add the random number.
103 update_option( 'cartflows-assets-version', time() );
104
105 $response_data = array(
106 'messsage' => __( 'Successfully deleted the dynamic CSS keys!', 'cartflows' ),
107 );
108 wp_send_json_success( $response_data );
109 }
110
111 /**
112 * Save settings.
113 *
114 * @return void
115 */
116 public function save_global_settings() {
117
118 $response_data = array( 'messsage' => $this->get_error_msg( 'permission' ) );
119
120 if ( ! current_user_can( 'cartflows_manage_settings' ) ) {
121 wp_send_json_error( $response_data );
122 }
123
124 if ( empty( $_POST ) ) {
125 $response_data = array( 'messsage' => __( 'No post data found!', 'cartflows' ) );
126 wp_send_json_error( $response_data );
127 }
128
129 /**
130 * Nonce verification
131 */
132 if ( ! check_ajax_referer( 'cartflows_save_global_settings', 'security', false ) ) {
133 $response_data = array( 'messsage' => $this->get_error_msg( 'nonce' ) );
134 wp_send_json_error( $response_data );
135 }
136
137
138 $setting_tab = isset( $_POST['setting_tab'] ) ? sanitize_text_field( wp_unslash( $_POST['setting_tab'] ) ) : '';
139
140 switch ( $setting_tab ) {
141
142 case 'general':
143 $this->save_general_settings();
144 break;
145
146 case 'permalink':
147 $this->save_permalink_settings();
148 break;
149
150 case 'other':
151 $this->save_other_settings();
152 break;
153
154 case 'user_role_manager':
155 $this->save_user_roles_management_settings();
156 break;
157
158 case 'integrations':
159 $this->save_integration_settings();
160 break;
161
162 case 'global_scripts':
163 $this->save_global_scripts_settings();
164 break;
165
166 default:
167 $this->save_general_settings();
168
169 }
170
171 do_action( 'cartflows_admin_save_global_settings', $setting_tab, 'cartflows_save_global_settings' );
172
173
174
175 $response_data = array(
176 'messsage' => __( 'Successfully saved data!', 'cartflows' ),
177 );
178
179 // Name the fields that were dropped so the UI can say so rather than
180 // reporting a clean save the user did not get.
181 if ( ! empty( $this->skipped_fields ) ) {
182 $response_data['skipped_fields'] = $this->skipped_fields;
183 $response_data['messsage'] = sprintf(
184 /* translators: %s: comma separated list of setting names. */
185 __( 'Saved, but these were not updated because your account cannot edit scripts: %s', 'cartflows' ),
186 implode( ', ', $this->skipped_fields )
187 );
188 }
189
190 // If a tab handler queued a redirect (e.g. legacy-admin toggle was just
191 // enabled), pass it through so the client hard-navigates after save.
192 if ( ! empty( $this->pending_redirect ) ) {
193 $response_data['redirect_to'] = $this->pending_redirect;
194 $response_data['message'] = __( 'Switching to the legacy CartFlows UI…', 'cartflows' );
195 }
196
197 wp_send_json_success( $response_data );
198 }
199
200 /**
201 * Save integration tab settings. Such as Facebook & Google Analytics and Google AutoAddress.
202 *
203 * Note: Called from save_global_settings function.
204 *
205 * @return void
206 */
207 public function save_integration_settings() {
208
209 $new_settings = array();
210
211 if ( isset( $_POST['_cartflows_facebook'] ) ) { //phpcs:ignore
212 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_facebook'] ) ); //phpcs:ignore
213 AdminHelper::update_admin_settings_option( '_cartflows_facebook', $new_settings, false );
214 }
215
216 if ( isset( $_POST['_cartflows_google_analytics'] ) ) { //phpcs:ignore
217 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_google_analytics'] ) ); //phpcs:ignore
218 AdminHelper::update_admin_settings_option( '_cartflows_google_analytics', $new_settings, false );
219 }
220
221 if ( isset( $_POST['_cartflows_google_auto_address'] ) ) { //phpcs:ignore
222 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_google_auto_address'] ) ); //phpcs:ignore
223 AdminHelper::update_admin_settings_option( '_cartflows_google_auto_address', $new_settings, false );
224 }
225
226 if ( isset( $_POST['_cartflows_tiktok'] ) ) { //phpcs:ignore
227 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_tiktok'] ) ); //phpcs:ignore
228 AdminHelper::update_admin_settings_option( '_cartflows_tiktok', $new_settings, false );
229 }
230
231 if ( isset( $_POST['_cartflows_pinterest'] ) ) { //phpcs:ignore
232 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_pinterest'] ) ); //phpcs:ignore
233 AdminHelper::update_admin_settings_option( '_cartflows_pinterest', $new_settings, false );
234 }
235
236 if ( isset( $_POST['_cartflows_google_ads'] ) ) { //phpcs:ignore
237 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_google_ads'] ) ); //phpcs:ignore
238 AdminHelper::update_admin_settings_option( '_cartflows_google_ads', $new_settings, false );
239 }
240
241 if ( isset( $_POST['_cartflows_snapchat'] ) ) { //phpcs:ignore
242 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_snapchat'] ) ); //phpcs:ignore
243 AdminHelper::update_admin_settings_option( '_cartflows_snapchat', $new_settings, false );
244 }
245 }
246
247 /**
248 * Save global CSS & Scripts settings.
249 *
250 * Note: Called from save_global_settings function.
251 *
252 * Note: Global scripts are stored in the '_cartflows_global_scripts' option and are
253 * completely independent of the per-flow/per-step custom script migration. They always
254 * use CodeMirror code editor fields regardless of the migration status
255 * ('cartflows_script_migration_status'). No migration is needed for global scripts.
256 *
257 * @return void
258 */
259 public function save_global_scripts_settings() {
260
261 /**
262 * Nonce verification
263 */
264 if ( ! check_ajax_referer( 'cartflows_save_global_settings', 'security', false ) ) {
265 $response_data = array( 'message' => __( 'Nonce validation failed', 'cartflows' ) );
266 wp_send_json_error( $response_data );
267 }
268
269 // Global CSS/JS are output raw on every CartFlows page. Restrict authoring to users
270 // with `unfiltered_html` so per-plugin caps cannot grant script write access to lower roles.
271 // Record what was dropped — returning silently here let save_global_settings() go on
272 // to report a success the user did not get.
273 if ( ! current_user_can( 'unfiltered_html' ) ) {
274 if ( isset( $_POST['_cartflows_global_scripts']['global_css'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
275 $this->skipped_fields[] = __( 'Global CSS', 'cartflows' );
276 }
277
278 if ( isset( $_POST['_cartflows_global_scripts']['global_js'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
279 $this->skipped_fields[] = __( 'Global JS', 'cartflows' );
280 }
281
282 return;
283 }
284
285 if ( isset( $_POST['_cartflows_global_scripts'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
286 $raw_settings = wp_unslash( $_POST['_cartflows_global_scripts'] ); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
287
288 $new_settings = array();
289
290 if ( isset( $raw_settings['global_css'] ) ) {
291 // Decode first to prevent double-encoding on repeated saves, then encode once.
292 $new_settings['global_css'] = htmlentities( html_entity_decode( $raw_settings['global_css'], ENT_QUOTES, 'UTF-8' ), ENT_QUOTES, 'UTF-8' );
293 }
294
295 if ( isset( $raw_settings['global_js'] ) ) {
296 // Decode first to prevent double-encoding on repeated saves, then encode once.
297 $new_settings['global_js'] = htmlentities( html_entity_decode( $raw_settings['global_js'], ENT_QUOTES, 'UTF-8' ), ENT_QUOTES, 'UTF-8' );
298 }
299
300 AdminHelper::update_admin_settings_option( '_cartflows_global_scripts', $new_settings, false );
301 }
302 }
303
304 /**
305 * Save other tab settings.
306 *
307 * Note: Called from save_global_settings function.
308 *
309 * @return void
310 */
311 public function save_other_settings() {
312
313 /**
314 * Nonce verification
315 */
316 if ( ! check_ajax_referer( 'cartflows_save_global_settings', 'security', false ) ) {
317 $response_data = array( 'messsage' => __( 'Nonce validation failed', 'cartflows' ) );
318 wp_send_json_error( $response_data );
319 }
320
321 $delete_plugin_data = '';
322
323 if ( isset( $_POST['cartflows_delete_plugin_data'] ) ) {
324 $delete_plugin_data = sanitize_text_field( $_POST['cartflows_delete_plugin_data'] );
325 }
326
327 AdminHelper::update_admin_settings_option( 'cartflows_delete_plugin_data', $delete_plugin_data, false );
328
329 if ( _is_cartflows_pro() ) {
330 AdminHelper::update_admin_settings_option( 'cartflows_pro_delete_plugin_data', $delete_plugin_data, false );
331 }
332
333 if ( isset( $_POST['cartflows_stats_report_emails'] ) ) {
334 $enable_report_emails = sanitize_text_field( $_POST['cartflows_stats_report_emails'] );
335 AdminHelper::update_admin_settings_option( 'cartflows_stats_report_emails', $enable_report_emails, false );
336 }
337
338 if ( isset( $_POST['cartflows_stats_report_email_ids'] ) ) {
339
340 if ( ! empty( $_POST['cartflows_stats_report_email_ids'] ) ) {
341 // Ignoring the sanitization rule as we are sanitizing each email before saving it.
342 $emails = preg_split( "/[\f\r\n]+/", $_POST['cartflows_stats_report_email_ids'] ); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
343 $validated_emails = array();
344
345 foreach ( $emails as $email_id ) {
346
347 if ( is_email( $email_id ) ) {
348 array_push( $validated_emails, sanitize_email( $email_id ) );
349 }
350 }
351 $validated_emails = implode( "\n", $validated_emails );
352 AdminHelper::update_admin_settings_option( 'cartflows_stats_report_email_ids', $validated_emails, false );
353 } else {
354 AdminHelper::update_admin_settings_option( 'cartflows_stats_report_email_ids', '', false );
355 }
356 }
357
358 // Update the option to enable/disable the non sensitive data tracking.
359 if ( isset( $_POST['cf_usage_optin'] ) ) {
360 $enable_non_sensative_data_tracking = sanitize_text_field( $_POST['cf_usage_optin'] );
361 AdminHelper::update_admin_settings_option( 'cf_usage_optin', $enable_non_sensative_data_tracking, true );
362 }
363
364 // Persist the legacy-admin toggle. When it transitions from any non-enabled
365 // value to 'enable', queue a redirect so the dashboard hard-reloads through
366 // the legacy loader on the next request. The redirect is merged into the
367 // final response by save_global_settings() AFTER do_action() and any other
368 // side-effects fire, so we don't bypass third-party hooks. The legacy value
369 // can be stored as boolean true (1.6.0 migration) — treat any truthy form as
370 // "previously enabled" so re-saving doesn't re-trigger the redirect.
371 if ( isset( $_POST['cartflows-legacy-admin'] ) ) {
372 $prev_raw = get_option( 'cartflows-legacy-admin', '' );
373 $prev_was_enabled = in_array( $prev_raw, array( true, 1, '1', 'enable', 'yes' ), true );
374 $legacy_admin = in_array( $_POST['cartflows-legacy-admin'], array( 'enable', 'disable' ), true )
375 ? sanitize_text_field( wp_unslash( $_POST['cartflows-legacy-admin'] ) )
376 : 'disable';
377
378 update_option( 'cartflows-legacy-admin', $legacy_admin );
379
380 if ( 'enable' === $legacy_admin && ! $prev_was_enabled ) {
381 $this->pending_redirect = admin_url( 'admin.php?page=' . CARTFLOWS_SLUG );
382 }
383 }
384 }
385
386 /**
387 * Save common settings of CartFlows. Like Default Page Builder and Store Checkout.
388 *
389 * Note: Called from save_global_settings function.
390 *
391 * @return void
392 */
393 public function save_general_settings() {
394
395 /**
396 * Nonce verification
397 */
398 if ( ! check_ajax_referer( 'cartflows_save_global_settings', 'security', false ) ) {
399 $response_data = array( 'messsage' => __( 'Nonce validation failed', 'cartflows' ) );
400 wp_send_json_error( $response_data );
401 }
402
403 $new_settings = array();
404
405 if ( isset( $_POST['_cartflows_common'] ) ) {
406 // Ignoring the sanitization rule as we are sanitizing input using our function.
407 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_common'] ) ); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
408 }
409
410 $common_settings = AdminHelper::get_admin_settings_option( '_cartflows_common', false, false );
411 $new_settings = wp_parse_args( $new_settings, $common_settings );
412
413 AdminHelper::update_admin_settings_option( '_cartflows_common', $new_settings, false );
414 }
415
416 /**
417 * Remove cf caps.
418 *
419 * @param object $user_role_obj user role object.
420 *
421 * @return void
422 */
423 public function remove_all_cf_cap( $user_role_obj ) {
424
425 $cf_cap = array(
426 'cartflows_manage_settings',
427 'cartflows_manage_flows_steps',
428 );
429
430 foreach ( $cf_cap as $cap ) {
431 $user_role_obj->remove_cap( $cap );
432 }
433 }
434
435 /**
436 * Add cf caps.
437 *
438 * @param object $user_role_obj user role object.
439 * @param string $access_key access key.
440 *
441 * @return void
442 */
443 public function add_selected_cf_cap( $user_role_obj, $access_key ) {
444
445 // Security: Validate access_key against allowlist to prevent privilege escalation.
446 $allowed_keys = array( 'access_to_cartflows', 'access_to_flows_and_step', 'no_access' );
447
448 if ( ! in_array( $access_key, $allowed_keys, true ) ) {
449 return;
450 }
451
452 switch ( $access_key ) {
453
454 case 'access_to_cartflows':
455 $user_role_obj->add_cap( 'cartflows_manage_settings' );
456 $user_role_obj->add_cap( 'cartflows_manage_flows_steps' );
457 break;
458
459 case 'access_to_flows_and_step':
460 $user_role_obj->add_cap( 'cartflows_manage_flows_steps' );
461 break;
462
463 default:
464 // No capabilities to add for 'no_access'.
465 break;
466
467 }
468 }
469
470 /**
471 * Add / Remove custom capability to the user role.
472 *
473 * @param array $new_settings Array of user role capability settings.
474 * @param array $old_settings Array of old user role capability settings.
475 *
476 * @return void
477 */
478 public function user_role_management( $new_settings, $old_settings ) {
479
480 // Security: Protect administrator role from modification via this endpoint.
481 $protected_roles = array( 'administrator' );
482
483 foreach ( $new_settings as $user_role => $access_key ) {
484
485 if ( in_array( $user_role, $protected_roles, true ) ) {
486 continue;
487 }
488
489 if ( ! isset( $old_settings[ $user_role ] ) || $old_settings[ $user_role ] !== $access_key ) {
490
491 $user_role_obj = get_role( $user_role );
492
493 if ( $user_role_obj ) {
494
495 $this->remove_all_cf_cap( $user_role_obj );
496
497 $this->add_selected_cf_cap( $user_role_obj, $access_key );
498
499 }
500 }
501 }
502 }
503
504 /**
505 * Save user role settings.
506 *
507 * Note: Called from save_global_settings function.
508 *
509 * @return void
510 */
511 public function save_user_roles_management_settings() {
512
513 /**
514 * Nonce verification
515 */
516 if ( ! check_ajax_referer( 'cartflows_save_global_settings', 'security', false ) ) {
517 $response_data = array( 'messsage' => __( 'Nonce validation failed', 'cartflows' ) );
518 wp_send_json_error( $response_data );
519 }
520
521 $new_settings = array();
522
523 if ( isset( $_POST['_cartflows_roles'] ) ) {
524 $new_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_roles'] ) ); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
525 }
526
527 $old_settings = AdminHelper::get_admin_settings_option( '_cartflows_roles' );
528
529 $new_settings = wp_parse_args( $new_settings, $old_settings );
530
531 AdminHelper::update_admin_settings_option( '_cartflows_roles', $new_settings, false );
532
533 // Add/Remove capability.
534 $this->user_role_management( $new_settings, $old_settings );
535 }
536
537 /**
538 * Save the permalink settings.
539 *
540 * Note: Called from save_global_settings function.
541 *
542 * @return void
543 */
544 public function save_permalink_settings() {
545
546 /**
547 * Nonce verification
548 */
549 if ( ! check_ajax_referer( 'cartflows_save_global_settings', 'security', false ) ) {
550 $response_data = array( 'messsage' => __( 'Nonce validation failed', 'cartflows' ) );
551 wp_send_json_error( $response_data );
552 }
553
554 if ( isset( $_POST['reset'] ) ) {
555 $_POST['_cartflows_permalink'] = array(
556 'permalink' => CARTFLOWS_STEP_PERMALINK_SLUG,
557 'permalink_flow_base' => CARTFLOWS_FLOW_PERMALINK_SLUG,
558 'permalink_structure' => '',
559 );
560
561 }
562 $new_settings = array();
563 if ( isset( $_POST['_cartflows_permalink'] ) ) {
564 $cartflows_permalink_settings = $this->sanitize_form_inputs( wp_unslash( $_POST['_cartflows_permalink'] ) ); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
565
566 if ( empty( $cartflows_permalink_settings['permalink'] ) ) {
567 $new_settings['permalink'] = CARTFLOWS_STEP_PERMALINK_SLUG;
568 } else {
569 $new_settings['permalink'] = $cartflows_permalink_settings['permalink'];
570 }
571
572 if ( empty( $cartflows_permalink_settings['permalink_flow_base'] ) ) {
573 $new_settings['permalink_flow_base'] = CARTFLOWS_FLOW_PERMALINK_SLUG;
574 } else {
575 $new_settings['permalink_flow_base'] = $cartflows_permalink_settings['permalink_flow_base'];
576 }
577
578 $new_settings['permalink_structure'] = $cartflows_permalink_settings['permalink_structure'];
579
580 }
581
582 AdminHelper::update_admin_settings_option( '_cartflows_permalink', $new_settings, true );
583
584 update_option( 'cartflows_permalink_refresh', true );
585 }
586
587
588 /**
589 * Save settings.
590 *
591 * @param array $input_settings settimg data.
592 */
593 public function sanitize_form_inputs( $input_settings = array() ) {
594 $new_settings = array();
595 foreach ( $input_settings as $key => $val ) {
596
597 if ( is_array( $val ) ) {
598 foreach ( $val as $k => $v ) {
599 $new_settings[ $key ][ $k ] = ( isset( $val[ $k ] ) ) ? sanitize_text_field( $v ) : '';
600 }
601 } else {
602 $new_settings[ $key ] = ( isset( $input_settings[ $key ] ) ) ? sanitize_text_field( $val ) : '';
603 }
604 }
605 return $new_settings;
606 }
607
608 /**
609 * Track KB search terms.
610 *
611 * @return void
612 */
613 public function track_kb_search() {
614 if ( ! current_user_can( 'cartflows_manage_settings' ) ) {
615 wp_send_json_error( array( 'message' => $this->get_error_msg( 'permission' ) ) );
616 }
617
618 /**
619 * Nonce verification
620 */
621 if ( ! check_ajax_referer( 'cartflows_track_kb_search', 'security', false ) ) {
622 $response_data = array( 'messsage' => __( 'Nonce validation failed', 'cartflows' ) );
623 wp_send_json_error( $response_data );
624 }
625
626 // Get search term.
627 $search_term = isset( $_POST['search_term'] ) ? sanitize_text_field( wp_unslash( $_POST['search_term'] ) ) : '';
628
629 if ( empty( $search_term ) ) {
630 wp_send_json_error( array( 'message' => 'Search term is empty' ) );
631 }
632
633 // Store the search term for BSF Analytics.
634 $kb_searches = get_option( 'cartflows_kb_searches', array() );
635
636 // Add the current search term to the list.
637 $kb_searches[] = $search_term;
638
639 // Keep only the last 20 searches to avoid data overload.
640 $kb_searches = array_slice( $kb_searches, -20 );
641
642 // Update the option.
643 update_option( 'cartflows_kb_searches', $kb_searches );
644
645 wp_send_json_success( array( 'message' => 'Search term tracked successfully' ) );
646 }
647
648 /**
649 * Handle on-demand custom script migration triggered by user clicking "Migrate Data".
650 *
651 * Verifies nonce and capabilities, calls the migration logic from Cartflows_Update,
652 * and returns a JSON response with the migrated post count.
653 *
654 * @since 2.2.2
655 * @return void
656 */
657 public function migrate_custom_scripts() {
658
659 $response_data = array( 'message' => $this->get_error_msg( 'permission' ) );
660
661 if ( ! current_user_can( 'cartflows_manage_settings' ) ) {
662 wp_send_json_error( $response_data );
663 }
664
665 if ( ! check_ajax_referer( 'cartflows_migrate_custom_scripts', 'security', false ) ) {
666 $response_data = array( 'message' => $this->get_error_msg( 'nonce' ) );
667 wp_send_json_error( $response_data );
668 }
669
670 $migrated_count = \Cartflows_Update::get_instance()->migrate_custom_scripts_on_demand();
671
672 wp_send_json_success(
673 array(
674 /* translators: %d: number of posts migrated */
675 'message' => sprintf( __( 'Migration completed successfully. %d post(s) migrated.', 'cartflows' ), $migrated_count ),
676 'migrated_count' => $migrated_count,
677 )
678 );
679 }
680 }
681