feat: migrate shipping to form-level and integrate flags.json as single source of truth
Shipping Migration: - Move shipping configuration from product-level to form-level - Add form shipping tab in form settings (no_shipping, flat_rate, free_shipping) - Update FlatRate to register at form level instead of product level - Update checkout logic to read from form settings - Support percentage-based flat rate calculation - Simplify shipping method IDs (flat_rate, free_shipping) Currency Flags Integration: - Add formipay_get_all_currency_flags() to read from admin/assets/json/flags.json - Remove hardcoded CURRENCY_FLAGS emoji map from VariationField.js - Create CurrencyFlag component to render base64 flag images - Localize currency_flags to window.formipayProductDetails - Update shipping info display in admin order details Benefits: - Form-level shipping prevents multiplying shipping costs per product - Single source of truth for currency flags (flags.json) - Better support for future cart system - Consistent with e-commerce standards
This commit is contained in:
@@ -33,6 +33,7 @@ class Product {
|
||||
|
||||
add_action( 'wp_ajax_formipay_product_get_currencies', [$this, 'formipay_product_get_currencies'] );
|
||||
add_action( 'wp_ajax_get_product_variables', [$this, 'get_product_variables'] );
|
||||
add_action( 'wp_ajax_get_product_attributes', [$this, 'get_product_attributes'] );
|
||||
|
||||
add_action('save_post', [$this, 'save_product'], 10, 2);
|
||||
|
||||
@@ -85,19 +86,26 @@ class Product {
|
||||
|
||||
public function add_submenu() {
|
||||
|
||||
add_submenu_page(
|
||||
'formipay',
|
||||
__( 'Products', 'formipay' ),
|
||||
__( 'Products', 'formipay' ),
|
||||
add_action( 'add_meta_boxes', [$this, 'add_react_metabox'] );
|
||||
add_action( 'admin_footer-post.php', [$this, 'render_react_metabox_template'] );
|
||||
add_action( 'admin_footer-post-new.php', [$this, 'render_react_metabox_template'] );
|
||||
add_action( 'save_post', [$this, 'save_product_metabox_fields'], 10, 2 );
|
||||
|
||||
add_submenu_page(
|
||||
'formipay',
|
||||
__('Products', 'formipay'),
|
||||
__('Products', 'formipay'),
|
||||
'manage_options',
|
||||
'formipay-products',
|
||||
[$this, 'formipay_products'],
|
||||
'formipay-products',
|
||||
[$this, 'formipay_product'],
|
||||
2
|
||||
);
|
||||
add_submenu_page(
|
||||
'formipay',
|
||||
__('Categories', 'formipay'),
|
||||
'└ ' . __('Categories', 'formipay'),
|
||||
'manage_options',
|
||||
|
||||
add_submenu_page(
|
||||
'formipay',
|
||||
__('Categories', 'formipay'),
|
||||
'└ ' . __('Categories', 'formipay'),
|
||||
'manage_options',
|
||||
'edit-tags.php?taxonomy=formipay-product-category&post_type=formipay-product',
|
||||
null,
|
||||
5
|
||||
@@ -105,6 +113,10 @@ class Product {
|
||||
|
||||
}
|
||||
|
||||
public function formipay_product() {
|
||||
\Formipay\Admin\ReactAdmin::render_mount_point('products');
|
||||
}
|
||||
|
||||
public function enqueue_admin() {
|
||||
// Assets now handled by ReactAdmin class
|
||||
return;
|
||||
@@ -227,9 +239,71 @@ class Product {
|
||||
|
||||
}
|
||||
|
||||
public function formipay_products_react() {
|
||||
|
||||
ReactAdmin::render_mount_point('products');
|
||||
}
|
||||
|
||||
public function add_react_metabox() {
|
||||
add_meta_box(
|
||||
'formipay_product_settings',
|
||||
__('Settings', 'formipay'),
|
||||
[$this, 'render_react_metabox'],
|
||||
'formipay-product',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
|
||||
public function render_react_metabox($post) {
|
||||
echo '<div data-formipay-field-renderer="product" data-post-id="' . esc_attr($post->ID) . '"></div>';
|
||||
}
|
||||
|
||||
public function render_react_metabox_template() {
|
||||
global $post;
|
||||
|
||||
if (!$post || $post->post_type !== 'formipay-product') {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = \Formipay\Admin\FieldConfigBridge::get_config_for_post($post->ID, $post->post_type);
|
||||
|
||||
// Get multi-currency settings from formipay_settings option
|
||||
$settings = get_option('formipay_settings', []);
|
||||
$is_multicurrency = !empty($settings['enable_multicurrency']);
|
||||
$multicurrencies = $settings['multicurrencies'] ?? [];
|
||||
|
||||
// Build global_selected_currencies from multicurrencies array
|
||||
$global_selected = [];
|
||||
foreach ($multicurrencies as $currency) {
|
||||
if (isset($currency['currency'])) {
|
||||
$code = explode(':::', $currency['currency'])[0];
|
||||
$global_selected[$code] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the same helper functions for consistency
|
||||
$global_currencies = get_global_currency_array();
|
||||
$default_currency = formipay_default_currency();
|
||||
|
||||
$product_details = [
|
||||
'multicurrency' => $is_multicurrency,
|
||||
'default_currency' => $default_currency,
|
||||
'global_currencies' => $global_currencies,
|
||||
'global_selected_currencies' => $global_selected,
|
||||
'currency_flags' => formipay_get_all_currency_flags(),
|
||||
];
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
window.formipayFieldConfig = <?php echo wp_json_encode($config); ?>;
|
||||
window.formipayProductDetails = <?php echo wp_json_encode($product_details); ?>;
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function formipay_products() {
|
||||
// React admin
|
||||
\Formipay\Admin\ReactAdmin::render_mount_point('products');
|
||||
|
||||
ReactAdmin::render_mount_point('products');
|
||||
}
|
||||
|
||||
public function cpt_post_fields_box($boxes) {
|
||||
@@ -242,13 +316,112 @@ class Product {
|
||||
}
|
||||
|
||||
public function cpt_post_fields_content($fields) {
|
||||
|
||||
|
||||
$fields['formipay_product_settings'] = array();
|
||||
|
||||
|
||||
$fields = apply_filters( 'formipay/product-config', $fields );
|
||||
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function save_product_metabox_fields($post_id, $post) {
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'update-post_' . $post_id)) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
if ($post->post_type !== 'formipay-product') {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
$meta_fields = [
|
||||
'product_type',
|
||||
'setting_product_price_regular',
|
||||
'setting_product_price_sale',
|
||||
'free_shipping',
|
||||
// Shipping fields
|
||||
'shipping_method',
|
||||
'flat_rate_type',
|
||||
'flat_rate_amount',
|
||||
'flat_rate_label',
|
||||
'free_shipping_label',
|
||||
'free_shipping_add_to_order_review',
|
||||
// Shipping dimensions (for carrier API calculation)
|
||||
'product_weight',
|
||||
'product_length',
|
||||
'product_width',
|
||||
'product_height',
|
||||
// Access and status
|
||||
'product_accesses',
|
||||
'product_access_to_email',
|
||||
'active',
|
||||
];
|
||||
|
||||
$global_currencies = get_global_currency_array();
|
||||
|
||||
foreach ($meta_fields as $field) {
|
||||
if (isset($_POST[$field])) {
|
||||
$value = wp_unslash($_POST[$field]);
|
||||
update_post_meta($post_id, $field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($global_currencies as $currency) {
|
||||
$symbol = formipay_get_currency_data_by_value($currency['currency'], 'symbol');
|
||||
|
||||
if (isset($_POST['setting_product_price_regular_' . $symbol])) {
|
||||
update_post_meta($post_id, 'setting_product_price_regular_' . $symbol, wp_unslash($_POST['setting_product_price_regular_' . $symbol]));
|
||||
}
|
||||
|
||||
if (isset($_POST['max_amount_' . $symbol])) {
|
||||
update_post_meta($post_id, 'max_amount_' . $symbol, wp_unslash($_POST['max_amount_' . $symbol]));
|
||||
}
|
||||
|
||||
// Save flat rate amounts per currency
|
||||
if (isset($_POST['flat_rate_amount_' . $symbol])) {
|
||||
update_post_meta($post_id, 'flat_rate_amount_' . $symbol, wp_unslash($_POST['flat_rate_amount_' . $symbol]));
|
||||
}
|
||||
}
|
||||
|
||||
// Save product variations (JSON from VariationField)
|
||||
if (isset($_POST['product_variations'])) {
|
||||
$variations_json = wp_unslash($_POST['product_variations']);
|
||||
$variations_data = json_decode($variations_json, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($variations_data)) {
|
||||
update_post_meta($post_id, 'product_variations', $variations_json);
|
||||
|
||||
// Also save the legacy format for backward compatibility
|
||||
if (isset($variations_data['variations']) && is_array($variations_data['variations'])) {
|
||||
$legacy_variations = $variations_data['variations'];
|
||||
update_post_meta($post_id, 'product_variables', wp_json_encode($legacy_variations));
|
||||
}
|
||||
|
||||
// Save attributes separately for legacy compatibility
|
||||
if (isset($variations_data['attributes']) && is_array($variations_data['attributes'])) {
|
||||
$legacy_attributes = [];
|
||||
foreach ($variations_data['attributes'] as $attr) {
|
||||
$legacy_attributes[] = [
|
||||
'attribute_name' => $attr['attribute_name'] ?? '',
|
||||
'attribute_type' => $attr['attribute_type'] ?? 'select',
|
||||
'attribute_variations' => $attr['attribute_variations'] ?? [],
|
||||
];
|
||||
}
|
||||
update_post_meta($post_id, 'product_variation_attributes', wp_json_encode($legacy_attributes));
|
||||
}
|
||||
} else {
|
||||
delete_post_meta($post_id, 'product_variations');
|
||||
}
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
public function general_config($fields) {
|
||||
@@ -434,7 +607,68 @@ class Product {
|
||||
|
||||
$product_currency_group = apply_filters( 'formipay/product-settings/tab:general/group:product-currency', $product_currency_group );
|
||||
|
||||
$general_all_fields = array_merge($product_details_group, $product_currency_group);
|
||||
// Shipping Dimensions Group (for physical products only)
|
||||
$shipping_dimensions_group = array(
|
||||
'setting_product_shipping_dimensions' => array(
|
||||
'type' => 'group_title',
|
||||
'label' => __( 'Shipping Dimensions', 'formipay' ),
|
||||
'description' => __( 'Weight and dimensions for carrier shipping calculation.', 'formipay' ),
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
),
|
||||
'group' => 'started'
|
||||
),
|
||||
'product_weight' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Weight (kg)', 'formipay' ),
|
||||
'step' => 0.01,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.00',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
)
|
||||
),
|
||||
'product_length' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Length (cm)', 'formipay' ),
|
||||
'step' => 0.1,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.0',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
)
|
||||
),
|
||||
'product_width' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Width (cm)', 'formipay' ),
|
||||
'step' => 0.1,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.0',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
)
|
||||
),
|
||||
'product_height' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Height (cm)', 'formipay' ),
|
||||
'step' => 0.1,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.0',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
),
|
||||
'group' => 'ended'
|
||||
),
|
||||
);
|
||||
|
||||
$shipping_dimensions_group = apply_filters( 'formipay/product-settings/tab:general/group:shipping-dimensions', $shipping_dimensions_group );
|
||||
|
||||
$general_all_fields = array_merge($product_details_group, $product_currency_group, $shipping_dimensions_group);
|
||||
|
||||
$general_all_fields = apply_filters( 'formipay/product-settings/tab:general', $general_all_fields );
|
||||
|
||||
@@ -448,77 +682,28 @@ class Product {
|
||||
}
|
||||
|
||||
public function variations_config($fields) {
|
||||
// Product Variations Attribute Group
|
||||
$product_attributes_group = array(
|
||||
// Product Variations - Unified React field with attributes and variations table
|
||||
$product_variations_group = array(
|
||||
'setting_product_variations' => array(
|
||||
'type' => 'group_title',
|
||||
'label' => __( 'Attributes', 'formipay' ),
|
||||
'description' => __( 'First we need to build the attribute of this product. For example Color, Size, License Site Count.', 'formipay' ),
|
||||
'label' => __( 'Product Variations', 'formipay' ),
|
||||
'description' => __( 'Configure attributes and their combinations to generate product variations with multi-currency pricing.', 'formipay' ),
|
||||
'group' => 'started'
|
||||
),
|
||||
'product_has_variation' => array(
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Product has variations', 'formipay' ),
|
||||
),
|
||||
'product_variation_attributes' => array(
|
||||
'type' => 'repeater',
|
||||
'label' => __('Attributes', 'formipay'),
|
||||
'description' => __( 'Your attributes will generate variation automatically.', 'formipay' ),
|
||||
'fields' => [
|
||||
'attribute_name' => [
|
||||
'type' => 'text',
|
||||
'label' => __( 'Attribute Name', 'formipay' ),
|
||||
'description' => __( 'e.g. Color, Size, etc', 'formipay' ),
|
||||
'is_group_title' => true
|
||||
],
|
||||
'attribute_variations' => [
|
||||
'type' => 'repeater',
|
||||
'label' => esc_html__( 'Variation', 'formipay' ),
|
||||
'fields' => [
|
||||
'variation_label' => [
|
||||
'type' => 'text',
|
||||
'label' => __( 'Title', 'formipay' ),
|
||||
'description' => __( 'e.g. Red, XL, etc', 'formipay' ),
|
||||
'required' => true,
|
||||
'is_group_title' => true
|
||||
],
|
||||
'variation_value' => [
|
||||
'type' => 'text',
|
||||
'label' => __( 'Value', 'formipay' ),
|
||||
'description' => __( 'e.g. red, xl, etc', 'formipay' ),
|
||||
'required' => true
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
'dependency' => array(
|
||||
'key' => 'product_has_variation',
|
||||
'value' => 'not_empty'
|
||||
),
|
||||
'product_variations' => array(
|
||||
'type' => 'variation',
|
||||
'label' => __( 'Variations', 'formipay' ),
|
||||
'description' => __( 'Add attributes (e.g., Color, Size) and their values. Variations will be generated automatically from all combinations.', 'formipay' ),
|
||||
'name' => 'product_variations',
|
||||
),
|
||||
);
|
||||
|
||||
$product_attributes_group = apply_filters( 'formipay/product-settings/tab:general/group:product-attributes', $product_attributes_group );
|
||||
$product_variations_group = apply_filters( 'formipay/product-settings/tab:variations/group:product-variations', $product_variations_group );
|
||||
|
||||
$last_product_attributes_group = array_key_last($product_attributes_group);
|
||||
$product_attributes_group[$last_product_attributes_group]['group'] = 'ended';
|
||||
$last_product_variations_group = array_key_last($product_variations_group);
|
||||
$product_variations_group[$last_product_variations_group]['group'] = 'ended';
|
||||
|
||||
// Product Variations Attribute Group
|
||||
|
||||
// Define your product variations field group somewhere in your plugin/theme
|
||||
$product_variations_table_html = file_get_contents(FORMIPAY_PATH . 'admin/templates/product-variations.php');
|
||||
|
||||
$product_variations_group = [
|
||||
'variation_table' => [
|
||||
'type' => 'html',
|
||||
'label' => __( 'Variations', 'formipay' ),
|
||||
'html' => $product_variations_table_html
|
||||
],
|
||||
];
|
||||
|
||||
$variation_all_fields = array_merge($product_attributes_group, $product_variations_group);
|
||||
|
||||
$variation_all_fields = apply_filters( 'formipay/product-settings/tab:variations', $variation_all_fields );
|
||||
$variation_all_fields = apply_filters( 'formipay/product-settings/tab:variations', $product_variations_group );
|
||||
|
||||
$fields['formipay_product_settings']['variation'] = array(
|
||||
'name' => __('Variations', 'formipay'),
|
||||
@@ -959,6 +1144,25 @@ class Product {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
public function get_product_attributes() {
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
|
||||
// Check permissions
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
wp_send_json_error(['message' => 'Unauthorized']);
|
||||
}
|
||||
|
||||
// Get attributes from legacy meta key
|
||||
$data = get_post_meta($post_id, 'product_variation_attributes', true);
|
||||
$json = is_string($data) ? json_decode($data, true) : $data;
|
||||
|
||||
if (is_array($json)) {
|
||||
wp_send_json_success($json);
|
||||
}
|
||||
|
||||
wp_send_json_success([]);
|
||||
}
|
||||
|
||||
public function save_product_depracated($post_id, $post) {
|
||||
// Verify nonce and permissions here if you have a nonce field (recommended)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user