PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.2.1
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.2.1
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.2.1, at admin-core/ajax/common-settings.php

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