| @@ -222,8 +222,12 @@ | ||
| 222 | 222 | // Ignoring nonce verification as using SuperGlobal variables on WordPress hooks. |
| 223 | 223 | if ( isset( $_GET['page'] ) && ( 'cartflows' === sanitize_text_field( $_GET['page'] ) || false !== strpos( sanitize_text_field( $_GET['page'] ), 'cartflows_' ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 224 | 224 | |
| 225 | 225 | add_action( 'admin_enqueue_scripts', array( $this, 'styles_scripts' ) ); |
| 226 | + | |
| 227 | + // Twemoji rewrites emoji text nodes inside our React tree, detaching nodes React still tracks and crashing the editor. | |
| 228 | + remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); | |
| 229 | + remove_action( 'admin_print_styles', 'print_emoji_styles' ); | |
| 226 | 230 | } |
| 227 | 231 | } |
| 228 | 232 | |
| 229 | 233 | /** |
| @@ -276,8 +280,22 @@ | ||
| 276 | 280 | $capability, |
| 277 | 281 | 'admin.php?page=' . $this->menu_slug . '&path=analytics' |
| 278 | 282 | ); |
| 279 | 283 | |
| 284 | + // Persistent re-entry point until the guided setup is finished. | |
| 285 | + if ( current_user_can( 'manage_options' ) && '1' !== get_option( 'wcf_setup_complete', false ) ) { | |
| 286 | + $exit_step = get_option( 'wcf_exit_setup_step', '' ); | |
| 287 | + $resume_link = 'index.php?page=cartflows-onboarding' . ( ! empty( $exit_step ) && is_string( $exit_step ) ? '&step=' . sanitize_key( $exit_step ) : '' ); | |
| 288 | + | |
| 289 | + add_submenu_page( | |
| 290 | + $parent_slug, | |
| 291 | + __( 'Resume Setup', 'cartflows' ), | |
| 292 | + __( 'Resume Setup', 'cartflows' ), | |
| 293 | + $capability, | |
| 294 | + $resume_link | |
| 295 | + ); | |
| 296 | + } | |
| 297 | + | |
| 280 | 298 | if ( current_user_can( 'cartflows_manage_settings' ) ) { |
| 281 | 299 | |
| 282 | 300 | add_submenu_page( |
| 283 | 301 | $parent_slug, |
| @@ -354,10 +372,8 @@ | ||
| 354 | 372 | if ( $this->is_current_page( 'cartflows' ) ) { |
| 355 | 373 | include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/settings-app.php'; |
| 356 | 374 | } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-edit-flow' ) ) ) { |
| 357 | 375 | include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/editor-app.php'; |
| 358 | - } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-log' ) ) ) { | |
| 359 | - include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/debugger.php'; | |
| 360 | 376 | } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-license' ) ) && _is_cartflows_pro() ) { |
| 361 | 377 | do_action( 'cartflows_admin_log', 'wcf-license' ); |
| 362 | 378 | } else { |
| 363 | 379 | include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/404-error.php'; |
| @@ -432,20 +448,21 @@ | ||
| 432 | 448 | |
| 433 | 449 | $localize = apply_filters( |
| 434 | 450 | 'cartflows_admin_localized_vars', |
| 435 | 451 | array( |
| 436 | - 'current_user' => ! empty( wp_get_current_user()->user_firstname ) ? wp_get_current_user()->user_firstname : wp_get_current_user()->display_name, | |
| 437 | - 'cf_pro_status' => $cf_pro_status, | |
| 438 | - 'cf_pro_type' => 'free', | |
| 439 | - 'cf_pro_type_inactive' => $cf_pro_type_inactive, | |
| 440 | - 'woocommerce_status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ), | |
| 441 | - 'default_page_builder' => $page_builder, | |
| 442 | - 'required_plugins' => \Cartflows_Helper::get_plugins_groupby_page_builders(), | |
| 443 | - 'required_plugins_data' => $this->get_required_plugins_data(), | |
| 444 | - 'is_any_required_plugins_missing' => $this->get_any_required_plugins_status(), | |
| 445 | - 'admin_base_slug' => $this->menu_slug, | |
| 446 | - 'admin_base_url' => admin_url(), | |
| 447 | - 'title_length' => apply_filters( | |
| 452 | + 'current_user' => ! empty( wp_get_current_user()->user_firstname ) ? wp_get_current_user()->user_firstname : wp_get_current_user()->display_name, | |
| 453 | + 'cf_pro_status' => $cf_pro_status, | |
| 454 | + 'cf_pro_type' => 'free', | |
| 455 | + 'cf_pro_type_inactive' => $cf_pro_type_inactive, | |
| 456 | + 'woocommerce_status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ), | |
| 457 | + 'default_page_builder' => $page_builder, | |
| 458 | + 'required_plugins' => \Cartflows_Helper::get_plugins_groupby_page_builders(), | |
| 459 | + 'required_plugins_data' => $this->get_required_plugins_data(), | |
| 460 | + 'is_any_required_plugins_missing' => $this->get_any_required_plugins_status(), | |
| 461 | + 'admin_base_slug' => $this->menu_slug, | |
| 462 | + 'admin_base_url' => admin_url(), | |
| 463 | + 'is_onboarding_completed' => '1' === get_option( 'wcf_setup_complete', false ), | |
| 464 | + 'title_length' => apply_filters( | |
| 448 | 465 | 'cartflows_flows_steps_title_length', |
| 449 | 466 | array( |
| 450 | 467 | 'max' => 50, |
| 451 | 468 | 'display_length' => 40, |
| @@ -450,37 +467,43 @@ | ||
| 450 | 467 | 'max' => 50, |
| 451 | 468 | 'display_length' => 40, |
| 452 | 469 | ) |
| 453 | 470 | ), |
| 454 | - 'plugin_dir' => CARTFLOWS_URL, | |
| 455 | - 'admin_url' => admin_url( 'admin.php' ), | |
| 456 | - 'ajax_url' => admin_url( 'admin-ajax.php' ), | |
| 457 | - 'is_rtl' => is_rtl(), | |
| 458 | - 'home_slug' => $this->menu_slug, | |
| 459 | - 'is_pro' => _is_cartflows_pro(), | |
| 460 | - 'is_file_mod_disabled' => function_exists( 'wcf_file_mod_disabled' ) ? wcf_file_mod_disabled() : false, // Keep false by default if function not exists. | |
| 461 | - 'page_builder' => $page_builder, | |
| 462 | - 'page_builder_name' => $page_builder_name, | |
| 463 | - 'global_checkout' => \Cartflows_Helper::get_common_setting( 'global_checkout' ), | |
| 464 | - 'flows_count' => 1, // Removing the flow count condition. | |
| 465 | - 'currentFlowSteps' => $current_flow_steps, | |
| 471 | + 'plugin_dir' => CARTFLOWS_URL, | |
| 472 | + 'admin_url' => admin_url( 'admin.php' ), | |
| 473 | + 'ajax_url' => admin_url( 'admin-ajax.php' ), | |
| 474 | + 'is_rtl' => is_rtl(), | |
| 475 | + 'home_slug' => $this->menu_slug, | |
| 476 | + 'is_pro' => _is_cartflows_pro(), | |
| 477 | + 'is_file_mod_disabled' => function_exists( 'wcf_file_mod_disabled' ) ? wcf_file_mod_disabled() : false, // Keep false by default if function not exists. | |
| 478 | + 'page_builder' => $page_builder, | |
| 479 | + 'page_builder_name' => $page_builder_name, | |
| 480 | + 'global_checkout' => \Cartflows_Helper::get_common_setting( 'global_checkout' ), | |
| 481 | + 'flows_count' => 1, // Removing the flow count condition. | |
| 482 | + 'currentFlowSteps' => $current_flow_steps, | |
| 466 | 483 | // Delete this code after 3 major update. Added in 1.10.4. |
| 467 | - 'license_status' => \_is_cartflows_pro_license_activated(), | |
| 468 | - 'license_popup_url' => admin_url( 'admin.php?page=cartflows&path=settings&tab=license' ), | |
| 469 | - 'store_checkout_show_product_tab' => \Cartflows_Helper::display_product_tab_in_store_checkout(), | |
| 470 | - 'cf_domain_url' => CARTFLOWS_DOMAIN_URL, | |
| 471 | - 'cf_upgrade_to_pro_url' => \Cartflows_Helper::get_upgrade_to_pro_link(), | |
| 472 | - 'logo_url' => esc_url_raw( CARTFLOWS_URL . 'assets/images/cartflows-logo.svg' ), | |
| 473 | - 'create_product_src' => $product_src, | |
| 474 | - 'cf_font_family' => AdminHelper::get_font_family(), | |
| 475 | - 'flows_and_steps' => ! empty( $flows_and_steps ) ? $flows_and_steps : '', | |
| 476 | - 'store_checkout_flows_and_steps' => \Cartflows_Helper::get_instance()->get_flows_and_steps( '', 'store-checkout' ), | |
| 477 | - 'woo_currency' => function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '', | |
| 478 | - 'template_library_url' => wcf()->get_site_url(), | |
| 479 | - 'image_placeholder' => esc_url_raw( CARTFLOWS_URL . 'admin-core/assets/images/image-placeholder.png' ), | |
| 480 | - 'google_fonts' => \CartFlows_Font_Families::get_google_fonts(), | |
| 481 | - 'system_fonts' => \CartFlows_Font_Families::get_system_fonts(), | |
| 482 | - 'font_weights' => array( | |
| 484 | + 'license_status' => \_is_cartflows_pro_license_activated(), | |
| 485 | + 'license_popup_url' => admin_url( 'admin.php?page=cartflows&path=settings&tab=license' ), | |
| 486 | + 'store_checkout_show_product_tab' => \Cartflows_Helper::display_product_tab_in_store_checkout(), | |
| 487 | + 'cf_domain_url' => CARTFLOWS_DOMAIN_URL, | |
| 488 | + 'cf_upgrade_to_pro_url' => \Cartflows_Helper::get_upgrade_to_pro_link(), | |
| 489 | + 'logo_url' => esc_url_raw( CARTFLOWS_URL . 'assets/images/cartflows-logo.svg' ), | |
| 490 | + 'create_product_src' => $product_src, | |
| 491 | + 'cf_font_family' => AdminHelper::get_font_family(), | |
| 492 | + 'flows_and_steps' => ! empty( $flows_and_steps ) ? $flows_and_steps : '', | |
| 493 | + 'store_checkout_flows_and_steps' => \Cartflows_Helper::get_instance()->get_flows_and_steps( '', 'store-checkout' ), | |
| 494 | + 'woo_currency' => function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '', | |
| 495 | + 'woo_currency_pos' => function_exists( 'wc' ) ? get_option( 'woocommerce_currency_pos', 'left' ) : 'left', | |
| 496 | + 'woo_decimal_sep' => function_exists( 'wc_get_price_decimal_separator' ) ? wc_get_price_decimal_separator() : '.', | |
| 497 | + 'woo_thousand_sep' => function_exists( 'wc_get_price_thousand_separator' ) ? wc_get_price_thousand_separator() : ',', | |
| 498 | + 'woo_decimals' => function_exists( 'wc_get_price_decimals' ) ? wc_get_price_decimals() : 2, | |
| 499 | + 'template_library_url' => wcf()->get_site_url(), | |
| 500 | + 'image_placeholder' => esc_url_raw( CARTFLOWS_URL . 'admin-core/assets/images/image-placeholder.png' ), | |
| 501 | + 'ai_connected' => class_exists( '\Cartflows_Ai_Auth' ) ? \Cartflows_Ai_Auth::get_instance()->get_auth_status() : false, | |
| 502 | + 'can_manage_ai_auth' => current_user_can( 'manage_options' ), | |
| 503 | + 'google_fonts' => \CartFlows_Font_Families::get_google_fonts(), | |
| 504 | + 'system_fonts' => \CartFlows_Font_Families::get_system_fonts(), | |
| 505 | + 'font_weights' => array( | |
| 483 | 506 | '100' => __( 'Thin 100', 'cartflows' ), |
| 484 | 507 | '200' => __( 'Extra-Light 200', 'cartflows' ), |
| 485 | 508 | '300' => __( 'Light 300', 'cartflows' ), |
| 486 | 509 | '400' => __( 'Normal 400', 'cartflows' ), |
| @@ -489,30 +512,44 @@ | ||
| 489 | 512 | '700' => __( 'Bold 700', 'cartflows' ), |
| 490 | 513 | '800' => __( 'Extra-Bold 800', 'cartflows' ), |
| 491 | 514 | '900' => __( 'Ultra-Bold 900', 'cartflows' ), |
| 492 | 515 | ), |
| 493 | - 'global_checkout_id' => $global_checkout_id ? absint( $global_checkout_id ) : '', | |
| 494 | - 'flow_action' => $flow_action, | |
| 495 | - 'step_action' => $step_action, | |
| 496 | - 'old_global_checkout' => get_option( '_cartflows_old_global_checkout', false ), | |
| 497 | - 'woopayments_status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ), | |
| 498 | - 'ca_status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ), | |
| 499 | - 'suretriggers_status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ), | |
| 500 | - 'moderncart_status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ), | |
| 501 | - 'cpsw_connection_status' => 'success' === get_option( 'cpsw_test_con_status', false ) || 'success' === get_option( 'cpsw_con_status', false ), | |
| 502 | - 'current_user_can_manage_cartflows' => current_user_can( 'cartflows_manage_settings' ), | |
| 503 | - 'is_set_report_email_ids' => get_option( 'cartflows_stats_report_email_ids', false ), | |
| 504 | - 'cf_docs_data' => get_option( 'cartflows_docs_data', false ), | |
| 505 | - 'woo_order_url' => $order_url, | |
| 506 | - 'integrations' => $this->get_recommendation_integrations(), | |
| 507 | - 'plugin_installer_nonce' => wp_create_nonce( 'updates' ), | |
| 508 | - 'instant_checkout_notice_status' => \Cartflows_Helper::get_admin_settings_option( 'wcf-instant-checkout-notice-skipped', false, false ), | |
| 509 | - 'cartflows_admin_notices' => $this->cartflows_admin_notices(), | |
| 510 | - 'whats_new_rss_feed' => $this->get_whats_new_rss_feeds_data(), | |
| 511 | - 'cartflows_current_version' => CARTFLOWS_VER, | |
| 512 | - 'cartflows_previous_versions' => \Cartflows_Helper::get_rollback_versions_options(), | |
| 513 | - 'rollback_url' => esc_url( add_query_arg( 'version', 'VERSION', wp_nonce_url( admin_url( 'admin-post.php?action=cartflows_rollback' ), 'cartflows_rollback' ) ) ), | |
| 514 | - 'utm_param_pro_plans' => 'utm_source=carflows-dashboard&utm_medium=free-cartflows&utm_campaign=go-pro', | |
| 516 | + 'global_checkout_id' => $global_checkout_id ? absint( $global_checkout_id ) : '', | |
| 517 | + 'flow_action' => $flow_action, | |
| 518 | + 'step_action' => $step_action, | |
| 519 | + 'old_global_checkout' => get_option( '_cartflows_old_global_checkout', false ), | |
| 520 | + 'woopayments_status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ), | |
| 521 | + 'ca_status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ), | |
| 522 | + 'suretriggers_status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ), | |
| 523 | + 'moderncart_status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ), | |
| 524 | + 'cpsw_connection_status' => 'success' === get_option( 'cpsw_test_con_status', false ) || 'success' === get_option( 'cpsw_con_status', false ), | |
| 525 | + 'current_user_can_manage_cartflows' => current_user_can( 'cartflows_manage_settings' ), | |
| 526 | + 'is_set_report_email_ids' => get_option( 'cartflows_stats_report_email_ids', false ), | |
| 527 | + 'woo_order_url' => $order_url, | |
| 528 | + 'integrations' => $this->get_recommendation_integrations(), | |
| 529 | + 'plugin_installer_nonce' => wp_create_nonce( 'updates' ), | |
| 530 | + 'instant_checkout_notice_status' => \Cartflows_Helper::get_admin_settings_option( 'wcf-instant-checkout-notice-skipped', false, false ), | |
| 531 | + 'cartflows_admin_notices' => $this->cartflows_admin_notices(), | |
| 532 | + 'whats_new_rss_feed' => $this->get_whats_new_rss_feeds_data(), | |
| 533 | + 'cartflows_current_version' => CARTFLOWS_VER, | |
| 534 | + 'cartflows_previous_versions' => \Cartflows_Helper::get_rollback_versions_options(), | |
| 535 | + 'rollback_url' => esc_url( add_query_arg( 'version', 'VERSION', wp_nonce_url( admin_url( 'admin-post.php?action=cartflows_rollback' ), 'cartflows_rollback' ) ) ), | |
| 536 | + 'utm_defaults' => \Cartflows_Helper::get_utm_defaults(), | |
| 537 | + /** | |
| 538 | + * Filters whether to hide already-added products from the checkout product search dropdown. | |
| 539 | + * | |
| 540 | + * When `true`, products already present in the current checkout step's product list are | |
| 541 | + * filtered out of the search results, preventing duplicate selection through the UI. | |
| 542 | + * | |
| 543 | + * Note: this is a UI-level restriction only. The server-side save path (REST/AJAX) is | |
| 544 | + * not gated by this filter, so duplicates submitted directly to the save endpoint will | |
| 545 | + * still be persisted. | |
| 546 | + * | |
| 547 | + * @since x.x.x | |
| 548 | + * | |
| 549 | + * @param bool $restrict Whether to hide already-added products in the search dropdown. Default false. | |
| 550 | + */ | |
| 551 | + 'restrict_duplicate_checkout_products' => apply_filters( 'cartflows_restrict_duplicate_checkout_products', false ), | |
| 515 | 552 | ) |
| 516 | 553 | ); |
| 517 | 554 | |
| 518 | 555 | // Load the codemirror for code editor. |
| @@ -792,8 +829,17 @@ | ||
| 792 | 829 | array(), |
| 793 | 830 | CARTFLOWS_VER |
| 794 | 831 | ); |
| 795 | 832 | |
| 833 | + // Store Checkout renders the canvas inside this app too, so it needs the React Flow base styles. | |
| 834 | + wp_register_style( | |
| 835 | + 'wcf-canvas-base-style', | |
| 836 | + $build_url . 'style-editor-app.css', | |
| 837 | + array(), | |
| 838 | + CARTFLOWS_VER | |
| 839 | + ); | |
| 840 | + wp_enqueue_style( 'wcf-canvas-base-style' ); | |
| 841 | + | |
| 796 | 842 | wp_enqueue_script( $handle ); |
| 797 | 843 | |
| 798 | 844 | wp_set_script_translations( $handle, 'cartflows' ); |
| 799 | 845 | |
| @@ -841,8 +887,17 @@ | ||
| 841 | 887 | array(), |
| 842 | 888 | CARTFLOWS_VER |
| 843 | 889 | ); |
| 844 | 890 | |
| 891 | + wp_register_style( | |
| 892 | + 'wcf-canvas-base-style', | |
| 893 | + $build_url . 'style-editor-app.css', | |
| 894 | + array(), | |
| 895 | + CARTFLOWS_VER | |
| 896 | + ); | |
| 897 | + // Enqueue the registered style. | |
| 898 | + wp_enqueue_style( 'wcf-canvas-base-style' ); | |
| 899 | + | |
| 845 | 900 | wp_enqueue_script( $handle ); |
| 846 | 901 | |
| 847 | 902 | wp_set_script_translations( $handle, 'cartflows' ); |
| 848 | 903 | |
| @@ -1149,10 +1204,19 @@ | ||
| 1149 | 1204 | |
| 1150 | 1205 | // Fetch the RSS feed from the URL. This saves us from the CORS issue. |
| 1151 | 1206 | $feed = wp_remote_retrieve_body( wp_safe_remote_get( 'https://cartflows.com/product/cartflows/feed/' ) ); // phpcs:ignore -- This is a valid use case cannot use VIP rules here. |
| 1152 | 1207 | |
| 1153 | - // Security: Set proper content type header and strip script tags to prevent XSS. | |
| 1154 | - echo $feed; // phpcs:ignore -- RSS feed content sanitized via wp_kses_post. | |
| 1208 | + /* | |
| 1209 | + * This is an RSS document, parsed as XML by the What's New client. It must not be | |
| 1210 | + * run through wp_kses_post() — that strips the feed's own elements and leaves the | |
| 1211 | + * client nothing to parse. Declaring the type is what stops a browser sniffing the | |
| 1212 | + * response as HTML; the body itself is fetched over HTTPS from our own domain and | |
| 1213 | + * this endpoint is behind a nonce and a capability check. | |
| 1214 | + */ | |
| 1215 | + header( 'Content-Type: application/rss+xml; charset=' . get_option( 'blog_charset' ) ); | |
| 1216 | + header( 'X-Content-Type-Options: nosniff' ); | |
| 1217 | + | |
| 1218 | + echo $feed; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- RSS/XML body, see above. | |
| 1155 | 1219 | exit; |
| 1156 | 1220 | } |
| 1157 | 1221 | } |
| 1158 | 1222 | |