PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
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
← All changes | includes/core/rest-api/Controllers/class-storecheckout-controller.php +649 -0 3.13.0 → 3.13.3 View file →
@@ -111,8 +111,58 @@
111 111 ),
112 112 )
113 113 );
114 114
115 + // One page of the React Store Checkout screen (`#/store-checkout`).
116 + register_rest_route(
117 + $this->namespace,
118 + '/' . $this->rest_base . '/listing',
119 + array(
120 + array(
121 + 'methods' => WP_REST_Server::READABLE,
122 + 'callback' => array( $this, 'get_listing' ),
123 + 'permission_callback' => array( $this, 'get_items_permissions_check' ),
124 + 'args' => array(
125 + 'page' => array(
126 + 'type' => 'integer',
127 + 'default' => 1,
128 + 'sanitize_callback' => 'absint',
129 + ),
130 + 'per_page' => array(
131 + 'type' => 'integer',
132 + 'default' => 25,
133 + 'sanitize_callback' => 'absint',
134 + ),
135 + 'search' => array(
136 + 'type' => 'string',
137 + 'default' => '',
138 + 'sanitize_callback' => 'sanitize_text_field',
139 + ),
140 + /*
141 + * Trash is a value of this filter rather than a tab of
142 + * its own, the way the React funnel list reads it — the
143 + * legacy screen had a separate `sc_status=trash` view.
144 + */
145 + 'funnel_status' => array(
146 + 'type' => 'string',
147 + 'default' => 'all',
148 + 'enum' => array( 'all', 'publish', 'draft', 'trash' ),
149 + ),
150 + 'orderby' => array(
151 + 'type' => 'string',
152 + 'default' => 'date',
153 + 'enum' => array( 'date', 'title', 'modified' ),
154 + ),
155 + 'order' => array(
156 + 'type' => 'string',
157 + 'default' => 'desc',
158 + 'enum' => array( 'asc', 'desc' ),
159 + ),
160 + ),
161 + ),
162 + )
163 + );
164 +
115 165 // Validate Store Checkout creation (always allowed in multi-checkout mode)
116 166 register_rest_route(
117 167 $this->namespace,
118 168 '/' . $this->rest_base . '/validate-creation',
@@ -534,8 +584,607 @@
534 584 return rest_ensure_response( array(
535 585 'success' => true,
536 586 'items' => $results,
537 587 ) );
588 + }
589 +
590 +
591 + /**
592 + * Free installs may keep this many store checkouts before the screen stops
593 + * offering to build another. Same number the legacy view hard-coded.
594 + */
595 + const FREE_STORE_CHECKOUT_LIMIT = 3;
596 +
597 +
598 + /**
599 + * One page of the React Store Checkout screen.
600 + *
601 + * Everything the screen needs travels with the listing — the analytics
602 + * strip, the status counts and the free-plan allowance — because all three
603 + * change with the rows and a second round trip would let them disagree with
604 + * what is on screen.
605 + *
606 + * Priority is worked out over the whole set rather than the page: it is a
607 + * funnel's position in the evaluation order of every store checkout on the
608 + * site, so paging must not renumber it.
609 + *
610 + * @param WP_REST_Request $request Full data about the request.
611 + *
612 + * @return WP_REST_Response
613 + * @since 3.13.6
614 + */
615 + public function get_listing( $request ) {
616 + $this->ensure_conditions_class();
617 +
618 + $page = max( 1, (int) $request->get_param( 'page' ) );
619 + $per_page = (int) $request->get_param( 'per_page' );
620 + $per_page = $per_page > 0 ? min( $per_page, 100 ) : 25;
621 + $search = (string) $request->get_param( 'search' );
622 + $status = (string) $request->get_param( 'funnel_status' );
623 + $orderby = (string) $request->get_param( 'orderby' );
624 + $order = 'asc' === $request->get_param( 'order' ) ? 'ASC' : 'DESC';
625 +
626 + $post_status = in_array( $status, array( 'publish', 'draft', 'trash' ), true )
627 + ? array( $status )
628 + : array( 'publish', 'draft', 'trash' );
629 +
630 + $matching = $this->query_store_checkouts( $post_status, $search, $orderby, $order );
631 + $total = count( $matching );
632 + $posts = array_slice( $matching, ( $page - 1 ) * $per_page, $per_page );
633 +
634 + /*
635 + * The evaluation order is a property of the live set, not of the filter
636 + * being looked at: a trashed checkout is evaluated by nobody, and a
637 + * search must not move the numbers of the rows it happens to match.
638 + */
639 + $priority = $this->get_priority_map();
640 +
641 + $items = array();
642 +
643 + foreach ( $posts as $funnel ) {
644 + $items[] = $this->prepare_row( $funnel, $priority );
645 + }
646 +
647 + return rest_ensure_response(
648 + array(
649 + 'success' => true,
650 + 'items' => $items,
651 + 'total' => $total,
652 + 'total_pages' => $per_page > 0 ? (int) ceil( $total / $per_page ) : 0,
653 + 'page' => $page,
654 + 'per_page' => $per_page,
655 + 'counts' => $this->get_listing_counts(),
656 + 'analytics' => $this->get_listing_analytics(),
657 + 'meta' => $this->get_listing_meta(),
658 + )
659 + );
660 + }
661 +
662 +
663 + /**
664 + * Every store checkout matching a status and a search, newest first.
665 + *
666 + * Resolved in full rather than paged by WP_Query because the screen also
667 + * needs the whole set to number the evaluation order, and asking twice for
668 + * the same posts is the more expensive of the two.
669 + *
670 + * @param array $post_status Post statuses to match.
671 + * @param string $search Search term, or an empty string.
672 + * @param string $orderby Order column.
673 + * @param string $order `ASC` or `DESC`.
674 + *
675 + * @return \WP_Post[]
676 + * @since 3.13.6
677 + */
678 + private function query_store_checkouts( $post_status, $search = '', $orderby = 'date', $order = 'DESC' ) {
679 + $args = array(
680 + 'post_type' => WPFNL_FUNNELS_POST_TYPE,
681 + 'posts_per_page' => -1,
682 + 'post_status' => $post_status,
683 + 'orderby' => $orderby,
684 + 'order' => $order,
685 + 'no_found_rows' => true,
686 + 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
687 + array(
688 + 'key' => '_wpfnl_funnel_type',
689 + 'value' => 'store_checkout',
690 + ),
691 + ),
692 + );
693 +
694 + if ( $search ) {
695 + $args['s'] = $search;
696 + }
697 +
698 + return get_posts( $args );
699 + }
700 +
701 +
702 + /**
703 + * Where each live store checkout sits in the evaluation order.
704 + *
705 + * The same ordering the matcher itself applies: conditional checkouts are
706 + * tried newest first, and the catch-all is the fallback when none of them
707 + * match. Only the newest catch-all can ever be reached — the ones behind it
708 + * are overridden, and the screen says so rather than showing a number that
709 + * means nothing.
710 + *
711 + * @return array `[ 'position' => int[], 'overridden' => int[], 'catchAll' => int[] ]`.
712 + * @since 3.13.6
713 + */
714 + private function get_priority_map() {
715 + $funnels = Wpfnl_Store_Checkout_Conditions::get_all_store_checkout_funnels();
716 + $conditional = array();
717 + $catch_all = array();
718 +
719 + foreach ( $funnels as $funnel ) {
720 + $step_id = Wpfnl_Store_Checkout_Conditions::get_checkout_step_id_for_funnel( $funnel->ID );
721 + $condition = $step_id
722 + ? Wpfnl_Store_Checkout_Conditions::get_condition( $step_id )
723 + : array( 'condition_type' => 'all' );
724 +
725 + if ( Wpfnl_Store_Checkout_Conditions::is_catch_all( $condition ) ) {
726 + $catch_all[] = (int) $funnel->ID;
727 + } else {
728 + $conditional[] = (int) $funnel->ID;
729 + }
730 + }
731 +
732 + $position = array();
733 + $overridden = array();
734 + $next = 1;
735 +
736 + foreach ( $conditional as $funnel_id ) {
737 + $position[ $funnel_id ] = $next++;
738 + }
739 +
740 + foreach ( $catch_all as $index => $funnel_id ) {
741 + $position[ $funnel_id ] = $next++;
742 +
743 + if ( $index > 0 ) {
744 + $overridden[] = $funnel_id;
745 + }
746 + }
747 +
748 + return array(
749 + 'position' => $position,
750 + 'overridden' => $overridden,
751 + 'catchAll' => $catch_all,
752 + );
753 + }
754 +
755 +
756 + /**
757 + * Shape one row of the listing.
758 + *
759 + * @param \WP_Post $funnel Store checkout funnel.
760 + * @param array $priority Output of get_priority_map().
761 + *
762 + * @return array
763 + * @since 3.13.6
764 + */
765 + private function prepare_row( $funnel, $priority ) {
766 + $funnel_id = (int) $funnel->ID;
767 + $step_id = Wpfnl_Store_Checkout_Conditions::get_checkout_step_id_for_funnel( $funnel_id );
768 + $condition = $step_id
769 + ? Wpfnl_Store_Checkout_Conditions::get_condition( $step_id )
770 + : array( 'condition_type' => 'all' );
771 +
772 + $is_trash = 'trash' === $funnel->post_status;
773 + $badges = $this->get_condition_badges( $condition );
774 +
775 + /*
776 + * A catch-all has no badge of its own, so the fallback one is added
777 + * here and the row is marked as having no real condition — which is
778 + * also what decides the wording of its priority.
779 + */
780 + $is_catch_all = empty( $badges );
781 +
782 + if ( $is_catch_all ) {
783 + $badges[] = array(
784 + 'label' => __( 'No Condition', 'wpfnl' ),
785 + 'tooltip' => __( 'This store checkout will apply as the default WooCommerce checkout without any condition.', 'wpfnl' ),
786 + 'isDate' => false,
787 + );
788 + }
789 +
790 + $summary = '';
791 +
792 + foreach ( $badges as $index => $badge ) {
793 + if ( $index > 0 ) {
794 + $summary .= empty( $badge['isDate'] ) ? ' OR ' : ' AND ';
795 + }
796 +
797 + $summary .= $badge['label'];
798 + }
799 +
800 + $view_url = '';
801 +
802 + if ( ! $is_trash && $step_id ) {
803 + $permalink = get_the_permalink( $step_id );
804 + $view_url = $permalink ? esc_url_raw( $permalink ) : '';
805 + }
806 +
807 + return array(
808 + 'id' => $funnel_id,
809 + 'name' => $funnel->post_title ? $funnel->post_title : __( '(no title)', 'wpfnl' ),
810 + 'status' => $funnel->post_status,
811 + 'statusLabel' => $this->get_status_label( $funnel->post_status ),
812 + 'builder' => Wpfnl_functions::get_page_builder_by_step_id( $funnel_id ),
813 + 'stepId' => $step_id ? (int) $step_id : 0,
814 + 'createdAt' => $funnel->post_date_gmt,
815 + // Rendered server-side so the column reads in the site's own
816 + // locale and date format rather than the browser's.
817 + 'createdLabel' => get_the_date( 'Y-m-d g:i A', $funnel_id ),
818 + 'conditionSummary' => $summary,
819 + 'conditionTooltip' => implode( ' ', wp_list_pluck( $badges, 'tooltip' ) ),
820 + 'hasCondition' => ! $is_catch_all,
821 + 'editUrl' => $is_trash ? '' : $this->get_funnel_edit_url( $funnel_id ),
822 + 'viewUrl' => $view_url,
823 + ) + $this->get_row_priority( $funnel_id, $priority, $is_catch_all, $is_trash );
824 + }
825 +
826 +
827 + /**
828 + * The priority badge of one row: its wording, its tone and why.
829 + *
830 + * @param int $funnel_id Funnel id.
831 + * @param array $priority Output of get_priority_map().
832 + * @param bool $is_catch_all Whether the checkout carries no condition.
833 + * @param bool $is_trash Whether the checkout is in the trash.
834 + *
835 + * @return array
836 + * @since 3.13.6
837 + */
838 + private function get_row_priority( $funnel_id, $priority, $is_catch_all, $is_trash ) {
839 + // Nothing in the trash is ever evaluated, so it has no place in the order.
840 + if ( $is_trash || ! isset( $priority['position'][ $funnel_id ] ) ) {
841 + return array(
842 + 'priorityLabel' => '—',
843 + 'priorityTone' => 'none',
844 + 'priorityTooltip' => __( 'Trashed store checkouts are never evaluated.', 'wpfnl' ),
845 + );
846 + }
847 +
848 + if ( in_array( $funnel_id, $priority['overridden'], true ) ) {
849 + return array(
850 + 'priorityLabel' => __( 'Overridden', 'wpfnl' ),
851 + 'priorityTone' => 'overridden',
852 + 'priorityTooltip' => __( 'This fallback checkout is overridden by a newer fallback. Only the most recently created fallback is active when no conditional checkout matches.', 'wpfnl' ),
853 + );
854 + }
855 +
856 + if ( $is_catch_all ) {
857 + return array(
858 + 'priorityLabel' => __( 'Default', 'wpfnl' ),
859 + 'priorityTone' => 'fallback',
860 + 'priorityTooltip' => __( 'This checkout has no condition and acts as the default fallback. It will only be used when no other conditional store checkout matches the customer\'s cart.', 'wpfnl' ),
861 + );
862 + }
863 +
864 + $position = (int) $priority['position'][ $funnel_id ];
865 +
866 + if ( 1 === $position ) {
867 + return array(
868 + 'priorityLabel' => __( 'Highest', 'wpfnl' ),
869 + 'priorityTone' => 'active',
870 + 'priorityTooltip' => __( 'This is the most recently created conditional checkout. It will be evaluated first and takes priority over all other store checkouts.', 'wpfnl' ),
871 + );
872 + }
873 +
874 + return array(
875 + /* translators: %d: priority position number. */
876 + 'priorityLabel' => sprintf( __( '#%d', 'wpfnl' ), $position ),
877 + 'priorityTone' => 'active',
878 + /* translators: %d: priority position number. */
879 + 'priorityTooltip' => sprintf( __( 'This checkout has evaluation priority #%d. Store checkouts with conditions are evaluated from newest to oldest. The first match wins.', 'wpfnl' ), $position ),
880 + );
881 + }
882 +
883 +
884 + /**
885 + * One badge per rule a condition carries, plus the date range when it has
886 + * one. An empty list means the checkout is a catch-all.
887 + *
888 + * @param array $condition Stored condition.
889 + *
890 + * @return array List of `[ 'label' => string, 'tooltip' => string, 'isDate' => bool ]`.
891 + * @since 3.13.6
892 + */
893 + private function get_condition_badges( $condition ) {
894 + $labels = array(
895 + 'all' => __( 'No Condition', 'wpfnl' ),
896 + 'products' => __( 'Specific Products', 'wpfnl' ),
897 + 'categories' => __( 'Product Categories', 'wpfnl' ),
898 + 'tags' => __( 'Product Tags', 'wpfnl' ),
899 + 'date_range' => __( 'Date Range', 'wpfnl' ),
900 + 'rules' => __( 'Custom Rules', 'wpfnl' ),
901 + );
902 +
903 + $type = isset( $condition['condition_type'] ) ? $condition['condition_type'] : 'all';
904 + $badges = array();
905 + $rules = array();
906 +
907 + if ( 'rules' === $type && ! empty( $condition['rules'] ) && is_array( $condition['rules'] ) ) {
908 + $rules = $condition['rules'];
909 +
910 + foreach ( $rules as $rule ) {
911 + $rule_type = isset( $rule['type'] ) ? $rule['type'] : 'all';
912 +
913 + $badges[] = array(
914 + 'label' => isset( $labels[ $rule_type ] ) ? $labels[ $rule_type ] : $labels['all'],
915 + 'tooltip' => $this->get_rule_tooltip( $rule, count( $rules ) ),
916 + 'isDate' => false,
917 + );
918 + }
919 + }
920 +
921 + if ( empty( $condition['use_date_range'] ) ) {
922 + return $badges;
923 + }
924 +
925 + $badges[] = array(
926 + 'label' => $labels['date_range'],
927 + 'tooltip' => $this->get_date_tooltip( $condition, ! empty( $rules ) ),
928 + 'isDate' => true,
929 + );
930 +
931 + return $badges;
932 + }
933 +
934 +
935 + /**
936 + * What one rule means, with its product, category or tag ids resolved to
937 + * the names the reader gave them.
938 + *
939 + * @param array $rule One rule of a condition.
940 + * @param int $total_rules How many rules the condition carries.
941 + *
942 + * @return string
943 + * @since 3.13.6
944 + */
945 + private function get_rule_tooltip( $rule, $total_rules = 1 ) {
946 + $type = isset( $rule['type'] ) ? $rule['type'] : 'all';
947 + $values = isset( $rule['values'] ) ? (array) $rule['values'] : array();
948 + $or_note = $total_rules > 1 ? ' ' . __( '(OR with other rules)', 'wpfnl' ) : '';
949 +
950 + if ( ! $values ) {
951 + return __( 'This checkout will appear when the condition is met.', 'wpfnl' ) . $or_note;
952 + }
953 +
954 + $names = array();
955 +
956 + foreach ( $values as $value_id ) {
957 + $value_id = (int) $value_id;
958 +
959 + if ( 'products' === $type ) {
960 + $names[] = get_the_title( $value_id );
961 + continue;
962 + }
963 +
964 + $taxonomy = 'categories' === $type ? 'product_cat' : ( 'tags' === $type ? 'product_tag' : '' );
965 +
966 + if ( ! $taxonomy ) {
967 + continue;
968 + }
969 +
970 + $term = get_term( $value_id, $taxonomy );
971 + $names[] = ( $term && ! is_wp_error( $term ) ) ? $term->name : '#' . $value_id;
972 + }
973 +
974 + $names = implode( ', ', $names );
975 +
976 + switch ( $type ) {
977 + case 'products':
978 + /* translators: %s: comma separated product names. */
979 + return sprintf( __( 'This checkout will appear when the cart contains: %s.', 'wpfnl' ), $names ) . $or_note;
980 +
981 + case 'categories':
982 + /* translators: %s: comma separated category names. */
983 + return sprintf( __( 'This checkout will appear when the cart has products from: %s.', 'wpfnl' ), $names ) . $or_note;
984 +
985 + case 'tags':
986 + /* translators: %s: comma separated tag names. */
987 + return sprintf( __( 'This checkout will appear when the cart has products tagged: %s.', 'wpfnl' ), $names ) . $or_note;
988 + }
989 +
990 + return __( 'This checkout will appear when the condition is met.', 'wpfnl' ) . $or_note;
991 + }
992 +
993 +
994 + /**
995 + * What the date range of a condition means.
996 + *
997 + * A date range narrows whatever the rules already matched rather than
998 + * offering another way to match, so its wording says "additionally" when
999 + * there are rules alongside it.
1000 + *
1001 + * @param array $condition Stored condition.
1002 + * @param bool $has_rules Whether the condition also carries rules.
1003 + *
1004 + * @return string
1005 + * @since 3.13.6
1006 + */
1007 + private function get_date_tooltip( $condition, $has_rules ) {
1008 + $from = isset( $condition['date_from'] ) ? $condition['date_from'] : '';
1009 + $to = isset( $condition['date_to'] ) ? $condition['date_to'] : '';
1010 +
1011 + if ( $from && $to ) {
1012 + return $has_rules
1013 + /* translators: 1: start date, 2: end date. */
1014 + ? sprintf( __( 'Additionally, this checkout will only be active between %1$s and %2$s.', 'wpfnl' ), $from, $to )
1015 + /* translators: 1: start date, 2: end date. */
1016 + : sprintf( __( 'This checkout will only be active between %1$s and %2$s.', 'wpfnl' ), $from, $to );
1017 + }
1018 +
1019 + if ( $from ) {
1020 + return $has_rules
1021 + /* translators: %s: start date. */
1022 + ? sprintf( __( 'Additionally, this checkout will only be active from %s onwards.', 'wpfnl' ), $from )
1023 + /* translators: %s: start date. */
1024 + : sprintf( __( 'This checkout will only be active from %s onwards.', 'wpfnl' ), $from );
1025 + }
1026 +
1027 + if ( $to ) {
1028 + return $has_rules
1029 + /* translators: %s: end date. */
1030 + ? sprintf( __( 'Additionally, this checkout will only be active until %s.', 'wpfnl' ), $to )
1031 + /* translators: %s: end date. */
1032 + : sprintf( __( 'This checkout will only be active until %s.', 'wpfnl' ), $to );
1033 + }
1034 +
1035 + return __( 'This checkout has a date range condition.', 'wpfnl' );
1036 + }
1037 +
1038 +
1039 + /**
1040 + * Orders, revenue, average order value and bump revenue across every live
1041 + * store checkout.
1042 + *
1043 + * @return array
1044 + * @since 3.13.6
1045 + */
1046 + private function get_listing_analytics() {
1047 + global $wpdb;
1048 +
1049 + $analytics = array(
1050 + 'totalOrders' => 0,
1051 + 'totalRevenue' => 0,
1052 + 'aov' => 0,
1053 + 'orderBumpRevenue' => 0,
1054 + );
1055 +
1056 + $ids = wp_list_pluck( Wpfnl_Store_Checkout_Conditions::get_all_store_checkout_funnels(), 'ID' );
1057 + $ids = array_filter( array_map( 'absint', (array) $ids ) );
1058 +
1059 + if ( ! $ids ) {
1060 + return $analytics;
1061 + }
1062 +
1063 + $stats_table = $wpdb->prefix . 'wpfnl_stats';
1064 +
1065 + // A site that has never tracked an order has no stats table, and that
1066 + // is not an error worth failing the whole screen over.
1067 + if ( ! $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $stats_table ) ) ) { // phpcs:ignore
1068 + return $analytics;
1069 + }
1070 +
1071 + // Safe to interpolate: every id has been through absint().
1072 + $placeholders = implode( ',', $ids );
1073 +
1074 + $row = $wpdb->get_row(
1075 + $wpdb->prepare(
1076 + "SELECT COUNT(DISTINCT id) AS total_orders,
1077 + COALESCE(SUM(total_sales), 0) AS total_revenue,
1078 + COALESCE(SUM(orderbump_sales), 0) AS orderbump_revenue
1079 + FROM {$stats_table}
1080 + WHERE funnel_id IN ({$placeholders}) AND status IN (%s, %s)",
1081 + 'completed',
1082 + 'processing'
1083 + )
1084 + ); // phpcs:ignore
1085 +
1086 + if ( ! $row ) {
1087 + return $analytics;
1088 + }
1089 +
1090 + $analytics['totalOrders'] = (int) $row->total_orders;
1091 + $analytics['totalRevenue'] = round( (float) $row->total_revenue, 2 );
1092 + $analytics['orderBumpRevenue'] = round( (float) $row->orderbump_revenue, 2 );
1093 + $analytics['aov'] = $analytics['totalOrders'] > 0
1094 + ? round( $analytics['totalRevenue'] / $analytics['totalOrders'], 2 )
1095 + : 0;
1096 +
1097 + return $analytics;
1098 + }
1099 +
1100 +
1101 + /**
1102 + * How many store checkouts each value of the status filter would find.
1103 + *
1104 + * @return array
1105 + * @since 3.13.6
1106 + */
1107 + private function get_listing_counts() {
1108 + $publish = count( $this->query_store_checkouts( array( 'publish' ) ) );
1109 + $draft = count( $this->query_store_checkouts( array( 'draft' ) ) );
1110 + $trash = count( $this->query_store_checkouts( array( 'trash' ) ) );
1111 +
1112 + return array(
1113 + 'all' => $publish + $draft + $trash,
1114 + 'publish' => $publish,
1115 + 'draft' => $draft,
1116 + 'trash' => $trash,
1117 + );
1118 + }
1119 +
1120 +
1121 + /**
1122 + * State the screen's chrome depends on: the plan, the store checkout
1123 + * allowance and where the creation journey starts.
1124 + *
1125 + * @return array
1126 + * @since 3.13.6
1127 + */
1128 + private function get_listing_meta() {
1129 + $is_pro_active = (bool) apply_filters( 'wpfunnels/is_pro_license_activated', false );
1130 + $count = (int) Wpfnl_functions::count_store_checkout_funnels();
1131 +
1132 + return array(
1133 + 'isProActive' => $is_pro_active,
1134 + 'isWooActive' => Wpfnl_functions::is_wc_active(),
1135 + 'storeCheckoutCount' => $count,
1136 + 'maxStoreCheckouts' => $is_pro_active ? -1 : self::FREE_STORE_CHECKOUT_LIMIT,
1137 + 'limitReached' => ! $is_pro_active && $count >= self::FREE_STORE_CHECKOUT_LIMIT,
1138 + 'upgradeUrl' => 'https://getwpfunnels.com/pricing/',
1139 + );
1140 + }
1141 +
1142 +
1143 + /**
1144 + * Word for a post status, in this screen's terms.
1145 + *
1146 + * A store checkout is either taking over the WooCommerce checkout or it is
1147 + * not, so published and draft read as Enabled and Disabled here — which is
1148 + * what the legacy screen called them.
1149 + *
1150 + * @param string $status Post status.
1151 + *
1152 + * @return string
1153 + * @since 3.13.6
1154 + */
1155 + private function get_status_label( $status ) {
1156 + if ( 'publish' === $status ) {
1157 + return __( 'Enabled', 'wpfnl' );
1158 + }
1159 +
1160 + if ( 'trash' === $status ) {
1161 + return __( 'Trashed', 'wpfnl' );
1162 + }
1163 +
1164 + return __( 'Disabled', 'wpfnl' );
1165 + }
1166 +
1167 +
1168 + /**
1169 + * Canvas URL of a store checkout funnel.
1170 + *
1171 + * @param int $funnel_id Funnel id.
1172 + *
1173 + * @return string
1174 + * @since 3.13.6
1175 + */
1176 + private function get_funnel_edit_url( $funnel_id ) {
1177 + return esc_url_raw(
1178 + add_query_arg(
1179 + array(
1180 + 'page' => WPFNL_EDIT_FUNNEL_SLUG,
1181 + 'id' => $funnel_id,
1182 + 'step_id' => 0,
1183 + ),
1184 + admin_url( 'admin.php' )
1185 + )
1186 + );
538 1187 }
539 1188
540 1189
541 1190 /**