JezK
Edit File: checkout-link-data.php
<?php /** * CartFlows Checkout Link Data API. * * @package CartFlows Pro */ namespace CartflowsProAdmin\AdminCore\Api; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } use CartflowsProAdmin\AdminCore\Api\ApiBase; /** * Class CheckoutLinkData. */ class CheckoutLinkData extends ApiBase { /** * Route base. * * @var string */ protected $rest_base = '/admin/checkout-link-data/'; /** * Instance * * @access private * @var CheckoutLinkData|null Class object. * @since x.x.x */ private static $instance = null; /** * Initiator * * @since x.x.x * @return CheckoutLinkData initialized object of class. */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Register API routes. * * @since x.x.x * @return void */ public function register_routes() { $namespace = $this->get_api_namespace(); // Route 1: GET all published checkout steps. register_rest_route( $namespace, $this->rest_base . 'steps', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_checkout_steps' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), ) ); // Route 2: GET all published WooCommerce coupon codes. register_rest_route( $namespace, $this->rest_base . 'coupons', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_coupons' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), ) ); // Route 3: GET all published WooCommerce products. register_rest_route( $namespace, $this->rest_base . 'products', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_products' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), ) ); // Route 4: GET fields for a specific checkout step. register_rest_route( $namespace, $this->rest_base . 'fields/(?P<step_id>[\d]+)', array( 'args' => array( 'step_id' => array( 'description' => __( 'Checkout Step ID.', 'cartflows-pro' ), 'type' => 'integer', 'required' => true, ), ), array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_step_fields' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), ) ); } /** * Get all published CartFlows checkout steps. * * @since x.x.x * @return \WP_REST_Response|\WP_Error */ public function get_checkout_steps() { $args = array( 'post_type' => CARTFLOWS_STEP_POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => 'wcf-step-type', 'value' => 'checkout', ), ), 'orderby' => 'title', 'order' => 'ASC', ); $steps_query = new \WP_Query( $args ); $steps = array(); foreach ( $steps_query->posts as $post ) { if ( ! $post instanceof \WP_Post ) { continue; } $step_id = (int) $post->ID; $steps[] = array( 'id' => $step_id, 'title' => get_the_title( $step_id ), 'permalink' => get_permalink( $step_id ), ); } $response = new \WP_REST_Response( $steps ); $response->set_status( 200 ); return $response; } /** * Get all published WooCommerce coupon codes with their details. * * Returns an array of coupon objects including label, value, discount type, * amount, expiry date, and description — used to populate the coupon * dropdown and display selected coupon details in the generator UI. * * @since x.x.x * @return \WP_REST_Response */ public function get_coupons() { $args = array( 'post_type' => 'shop_coupon', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', 'fields' => 'ids', ); $coupon_ids = get_posts( $args ); $coupons = array(); $discount_type_labels = array( 'percent' => __( 'Percentage discount', 'cartflows-pro' ), 'fixed_cart' => __( 'Fixed cart discount', 'cartflows-pro' ), 'fixed_product' => __( 'Fixed product discount', 'cartflows-pro' ), ); $date_format_option = get_option( 'date_format', 'F j, Y' ); $date_format = is_string( $date_format_option ) && '' !== $date_format_option ? $date_format_option : 'F j, Y'; foreach ( $coupon_ids as $coupon_id ) { $coupon = new \WC_Coupon( $coupon_id ); $code = $coupon->get_code(); $expiry_date = $coupon->get_date_expires(); $coupons[] = array( 'label' => $code, 'value' => $code, 'discount_type' => isset( $discount_type_labels[ $coupon->get_discount_type() ] ) ? $discount_type_labels[ $coupon->get_discount_type() ] : $coupon->get_discount_type(), 'amount' => $coupon->get_amount(), 'expiry_date' => $expiry_date ? $expiry_date->date_i18n( $date_format ) : '', 'description' => $coupon->get_description(), ); } $response = new \WP_REST_Response( $coupons ); $response->set_status( 200 ); return $response; } /** * Get all published WooCommerce products for the checkout link generator. * * Returns simple and variable products so that store checkout links can * pre-select products to add to the cart when the link is visited. * * @since x.x.x * @return \WP_REST_Response */ public function get_products() { $products = array(); if ( ! wcf()->is_woo_active ) { return new \WP_REST_Response( $products ); } $args = array( 'status' => 'publish', 'limit' => -1, 'orderby' => 'title', 'order' => 'ASC', 'type' => array( 'simple', 'variable' ), ); $wc_products = wc_get_products( $args ); if ( ! is_array( $wc_products ) ) { return new \WP_REST_Response( $products ); } foreach ( $wc_products as $product ) { if ( $product->is_type( 'variable' ) ) { $variation_ids = $product->get_children(); foreach ( $variation_ids as $variation_id ) { $variation = wc_get_product( $variation_id ); if ( ! $variation instanceof \WC_Product_Variation ) { continue; } if ( ! $variation->is_in_stock() && ! $variation->backorders_allowed() ) { continue; } $attributes = $variation->get_variation_attributes(); $attribute_labels = array(); foreach ( $attributes as $attribute_name => $attribute_value ) { if ( '' === $attribute_value ) { continue; } $taxonomy = str_replace( 'attribute_', '', $attribute_name ); if ( taxonomy_exists( $taxonomy ) ) { $term = get_term_by( 'slug', $attribute_value, $taxonomy ); $attribute_labels[] = ( $term instanceof \WP_Term ) ? $term->name : $attribute_value; } else { $attribute_labels[] = $attribute_value; } } $label = $product->get_name(); if ( ! empty( $attribute_labels ) ) { $label .= ' — ' . implode( ' / ', $attribute_labels ); } $products[] = array( 'label' => $label, 'value' => $variation_id, ); } } else { $products[] = array( 'label' => $product->get_name(), 'value' => $product->get_id(), ); } } $response = new \WP_REST_Response( $products ); $response->set_status( 200 ); return $response; } /** * Get fields for a specific checkout step. * * @since x.x.x * @param \WP_REST_Request $request Full details about the request. * @return \WP_REST_Response|\WP_Error */ public function get_step_fields( $request ) { $step_id = absint( $request->get_param( 'step_id' ) ); // Validate step exists and is a checkout step. $step = get_post( $step_id ); if ( ! $step || 'publish' !== $step->post_status ) { return new \WP_Error( 'cartflows_rest_invalid_step', __( 'Invalid step ID.', 'cartflows-pro' ), array( 'status' => 404 ) ); } $step_type = get_post_meta( $step_id, 'wcf-step-type', true ); if ( 'checkout' !== $step_type ) { return new \WP_Error( 'cartflows_rest_invalid_step_type', __( 'The provided step is not a checkout step.', 'cartflows-pro' ), array( 'status' => 400 ) ); } $fields = $this->get_fields_for_step( $step_id ); $response = new \WP_REST_Response( $fields ); $response->set_status( 200 ); return $response; } /** * Build the list of available fields for a checkout step. * * Uses WC()->checkout()->get_checkout_fields() so that custom fields * registered via the woocommerce_checkout_fields filter (e.g. from * Checkout Field Editor or other plugins) are included automatically. * * @since x.x.x * @param int $step_id Checkout step ID. * @return array<int, array<string, mixed>> */ private function get_fields_for_step( $step_id ) { $fields = array(); if ( ! wcf()->is_woo_active ) { return $fields; } // Non-input field types that cannot be pre-filled via URL params. $skip_types = array( 'hidden', 'heading' ); // Standard WooCommerce address field keys — grouped into the Address UI card. $address_field_keys = array( 'billing_country', 'billing_address_1', 'billing_address_2', 'billing_city', 'billing_state', 'billing_postcode', 'shipping_country', 'shipping_address_1', 'shipping_address_2', 'shipping_city', 'shipping_state', 'shipping_postcode', ); // get_checkout_fields() applies woocommerce_checkout_fields, so // any custom fields added by third-party plugins are included. $checkout_fields = WC()->checkout()->get_checkout_fields(); $section_map = array( 'billing' => 'billing', 'shipping' => 'shipping', ); foreach ( $section_map as $wc_section => $section_key ) { if ( empty( $checkout_fields[ $wc_section ] ) ) { continue; } foreach ( $checkout_fields[ $wc_section ] as $key => $field ) { $field_type = isset( $field['type'] ) ? $field['type'] : 'text'; if ( in_array( $field_type, $skip_types, true ) ) { continue; } $field_data = array( 'id' => $key, 'label' => isset( $field['label'] ) ? $field['label'] : $key, 'section' => $section_key, 'type' => 'text', 'group' => in_array( $key, $address_field_keys, true ) ? 'address' : 'general', ); if ( 'country' === $field_type ) { $field_data['type'] = 'country'; $field_data['options'] = WC()->countries->get_countries(); // All states keyed by country code — used to populate the // state dropdown in the React UI without extra API calls. $field_data['states'] = WC()->countries->get_states(); } $fields[] = $field_data; } } // Virtual coupon field — handled by apply_url_coupon() on the frontend. $fields[] = array( 'id' => 'coupon', 'label' => __( 'Coupon Code', 'cartflows-pro' ), 'section' => 'other', 'type' => 'text', ); return $fields; } /** * Check whether a given request has permission to access this endpoint. * * @since x.x.x * @param \WP_REST_Request $request Full details about the request. * @return bool|\WP_Error */ public function get_item_permissions_check( $request ) { if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) { return new \WP_Error( 'cartflows_rest_cannot_view', __( 'Sorry, you cannot access this resource.', 'cartflows-pro' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } }