Files
formipay/includes/Coupon.php
dwindown 622c9f8eb7 feat: add React FieldRenderer system for settings and metaboxes
Complete React-based field rendering system that replaces WPCFTO Vue.js
layer while maintaining PHP field configuration compatibility.

Components:
- FieldRenderer: Main renderer with tabs support (metabox) and direct mode (settings)
- FieldTypes: 15+ field types (Text, Number, Select, Radio, Date, etc.)
- RepeaterField: Collapsible repeater with currency label parsing
- DependencyEngine: Show/hide fields based on conditions
- ValidationEngine: Client-side validation with error messages
- SettingsRenderer: Settings page with AJAX save to wp_options

Features:
- Repeater rows collapsed by default with readable currency titles
- Searchable dropdowns using Popover + Command pattern
- Proper label resolution for pre-selected values
- Hidden input sync for WordPress form submission

Also includes:
- FieldConfigBridge: Transform PHP configs to React format
- Updated Settings.php for React-based settings page
- Radio-group UI component
- wp-admin-restore.css for admin panel isolation
2026-04-28 16:48:08 +07:00

1291 lines
48 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Formipay;
use Formipay\Traits\SingletonTrait;
if ( ! defined( 'ABSPATH' ) ) exit;
class Coupon {
use SingletonTrait;
/**
* Initializes the plugin by setting filters and administration functions.
*/
protected function __construct() {
add_action( 'init', [$this, 'cpt'] );
add_action( 'admin_menu', [$this, 'add_submenu'] );
add_action( 'admin_enqueue_scripts', [$this, 'enqueue_admin'] );
add_filter( 'stm_wpcfto_boxes', [$this, 'cpt_post_fields_box'] );
add_filter( 'stm_wpcfto_fields', [$this, 'cpt_post_fields_content'] );
add_filter( 'formipay/coupon-config', [$this, 'rule_config'] );
add_filter( 'formipay/coupon-config', [$this, 'restriction_config'] );
add_filter( 'formipay/admin-editor/layout/static-elements-sort', [$this, 'add_static_element'] );
add_action( 'formipay/render/static-element', [$this, 'render_static_element'] );
add_action( 'wp_ajax_check_coupon_code', [$this, 'check_coupon_code'] );
add_action( 'wp_ajax_nopriv_check_coupon_code', [$this, 'check_coupon_code'] );
// Admin Page
add_action( 'wp_ajax_formipay_coupon_get_products', [$this, 'formipay_coupon_get_products'] );
add_action( 'wp_ajax_formipay-tabledata-coupons', [$this, 'formipay_tabledata_coupons'] );
add_action( 'wp_ajax_formipay-create-coupon-post', [$this, 'formipay_create_coupon_post'] );
add_action( 'wp_ajax_formipay-delete-coupon', [$this, 'formipay_delete_coupon'] );
add_action( 'wp_ajax_formipay-bulk-delete-coupon', [$this, 'formipay_bulk_delete_coupon'] );
add_action( 'wp_ajax_formipay-duplicate-coupon', [$this, 'formipay_duplicate_coupon'] );
add_action( 'wp_ajax_formipay-get-coupon', [$this, 'formipay_get_coupon'] );
add_action( 'wp_ajax_formipay-save-coupon', [$this, 'formipay_save_coupon'] );
add_action( 'wp_ajax_formipay-autocomplete-search', [$this, 'formipay_autocomplete_search'] );
// React Metabox
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'] );
// Save coupon data via WordPress save_post hook (regular hook with post type check inside)
add_action( 'save_post', [$this, 'save_coupon_on_post_update'], 10, 2 );
// Order
add_filter( 'formipay/order/order-details', [$this, 'order_details'], 99, 3 );
// Paypal
add_filter( 'formipay/order/paypal/payload/breakdown', [$this, 'add_discount_item'], 99, 3 );
}
public function cpt() {
$labels = array(
'name' => __('Coupons', 'formipay'),
'singular_name' => __('Coupon', 'formipay'),
// 'menu_name' => 'Formipay',
'add_new' => __('Add New', 'formipay'),
'add_new_item' => __('Add New Coupon', 'formipay'),
'edit' => __('Edit', 'formipay'),
'edit_item' => __('Edit Coupon', 'formipay'),
'new_item' => __('New Coupon', 'formipay'),
'view' => __('View', 'formipay'),
'view_item' => __('View Coupon', 'formipay'),
'search_items' => __('Search Coupon', 'formipay'),
'not_found' => __('No coupons found', 'formipay'),
'not_found_in_trash' => __('No coupons found in trash', 'formipay'),
'parent' => __('Coupon Parent', 'formipay')
);
$args = array(
'label' => 'Coupons',
'description' => __('Coupon for marketing campaign', 'formipay'),
'labels' => $labels,
'public' => false,
'supports' => array( 'title' ),
'hierarchical' => false,
'has_archive' => false,
'show_ui' => true,
'show_in_menu' => false,
'show_in_rest' => false,
'query_var' => true
);
register_post_type( 'formipay-coupon', $args );
}
public function add_submenu() {
add_submenu_page(
'formipay',
__( 'Coupons', 'formipay' ),
__( 'Coupons', 'formipay' ),
'manage_options',
'formipay-coupons',
[$this, 'formipay_coupon'],
);
}
public function formipay_coupon() {
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('coupons');
}
public function enqueue_admin() {
$screen = get_current_screen();
// Enqueue SweetAlert2 for coupon post edit screen
if ($screen && $screen->post_type === 'formipay-coupon' && $screen->base === 'post') {
wp_enqueue_style('sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.css', [], '11.14.4', 'all');
wp_enqueue_script('sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.js', ['jquery'], '11.14.4', true);
}
}
public function cpt_post_fields_box($boxes) {
// Disabled - using React metabox instead
// $boxes['formipay_coupon_settings'] = array(
// 'post_type' => array('formipay-coupon'),
// 'label' => __('Details', 'formipay'),
// );
return $boxes;
}
public function cpt_post_fields_content($fields) {
$fields['formipay_coupon_settings'] = array();
$fields = apply_filters( 'formipay/coupon-config', $fields );
return $fields;
}
public function rule_config($fields) {
$multicurrency = formipay_is_multi_currency_active();
$all_currencies = formipay_currency_as_options();
$global_selected_currencies = formipay_global_currency_options();
$global_currencies = formipay_global_currency_options('raw');
$default_currency = formipay_default_currency();
// Group : Rules
$rules_group_1 = array(
'rules_general_group' => array(
'type' => 'group_title',
'label' => __( 'General', 'formipay' ),
'group' => 'started'
),
'active' => array(
'type' => 'checkbox',
'label' => __( 'Active this coupon', 'formipay' ),
'value' => true,
),
'type' => array(
'type' => 'radio',
'label' => __( 'Type', 'formipay' ),
'required' => true,
'options' => array(
'fixed' => __( 'Fixed', 'formipay' ),
'percentage' => __( 'Percentage', 'formipay' )
),
'value' => 'fixed'
),
'amount_percentage' => array(
'type' => 'number',
'label' => __( 'Amount', 'formipay' ),
'required' => true,
'dependency' => array(
'key' => 'type',
'value' => 'percentage'
),
'group' => 'ended'
)
);
$rules_group_2 = [];
$global_currencies = get_global_currency_array();
$default_currency = formipay_default_currency();
$rules_group_2['discount_amount_wrapper'] = array(
'type' => 'group_title',
'label' => __( 'Discount Amount', 'formipay' ),
'dependency' => array(
'key' => 'type',
'value' => 'fixed'
),
'group' => 'started',
);
$rules_group_max['discount_max_amount_wrapper'] = array(
'type' => 'group_title',
'label' => __( 'Max Discount Amount', 'formipay' ),
'description' => __( 'Leave empty to not limit the max discount amount.', 'formipay' ),
'group' => 'started',
);
foreach($global_currencies as $currency){
$default_currency_symbol = formipay_get_currency_data_by_value($default_currency, 'symbol');
$currency_symbol = formipay_get_currency_data_by_value($currency['currency'], 'symbol');
$currency_title = ucwords(formipay_get_currency_data_by_value($currency['currency'], 'title'));
$decimal_digits = intval($currency['decimal_digits']);
$step = $decimal_digits > 0 ? pow(10, -$decimal_digits) : 1;
$rules_group_2['amount_fixed_'.$currency_symbol] = array(
'type' => 'number',
'label' => sprintf(
__( '<img src="%s" width="18" /> Amount in %s', 'formipay' ),
formipay_get_flag_by_currency($currency['currency']),
$currency_symbol
),
'step' => $step,
'min' => 0,
'required' => true,
'placeholder' => __( 'Enter Amount...', 'formipay' ),
'dependency' => array(
'key' => 'type',
'value' => 'fixed'
)
);
$rules_group_max['max_amount_'.$currency_symbol] = array(
'type' => 'number',
'label' => sprintf(
__( '<img src="%s" width="18" /> Max Amount in %s', 'formipay' ),
formipay_get_flag_by_currency($currency['currency']),
$currency_symbol
),
'step' => $step,
'min' => 0,
'placeholder' => __( 'Enter Max Amount...', 'formipay' ),
'dependency' => array(
'key' => 'type',
'value' => 'percentage'
)
);
}
$last_rules_group_2 = array_key_last($rules_group_2);
$rules_group_2[$last_rules_group_2]['group'] = 'ended';
$last_rules_group_max = array_key_last($rules_group_max);
$rules_group_max[$last_rules_group_max]['group'] = 'ended';
$rules_group_3 = array(
'rules_group' => array(
'type' => 'group_title',
'label' => __( 'Rules', 'formipay' ),
'description' => __( 'Define the rules of this coupon.', 'formipay' ),
'group' => 'started'
),
'case_sensitive' => array(
'type' => 'checkbox',
'label' => __( 'Case Sensitive', 'formipay' ),
'description' => __( 'If this activated, coupon codes must be entered with the exact capitalization as specified. <br>
For example code <B>SAVE10</b>:<br>
- <b>SAVE10</b> is valid<br>
- <b>save10</b> is invalid<br>
- <b>Save10</b> is invalid.', 'formipay' )
),
'free_shipping' => array(
'type' => 'checkbox',
'label' => __( 'Free Shipping', 'formipay' ),
'description' => __( 'Shipping cost will be free when this coupon applied.', 'formipay' )
),
'quantity_active' => array(
'type' => 'checkbox',
'label' => __( 'Influenced by Quantity', 'formipay' ),
'description' => __( 'Example: when buyer buy 4 pcs of item, 4 × discount amount will be implemented.', 'formipay' ),
'dependency' => array(
'key' => 'type',
'value' => 'fixed'
)
),
// 'max_discount' => array(
// 'type' => 'number',
// 'label' => __( 'Max Discount Amount', 'formipay' ),
// 'description' => __( 'Leave it empty to not limit the max discount amount.', 'formipay' )
// ),
);
$rules_group = $rules_group_1 + $rules_group_2 + $rules_group_max + $rules_group_3;
$rules_group = apply_filters( 'formipay/coupon-settings/tab:rules/group:rules', $rules_group );
$last_rules_group = array_key_last($rules_group);
$rules_group[$last_rules_group]['group'] = 'ended';
$all_group_merged = $rules_group;
$settings_all_fields = apply_filters( 'formipay/coupon-settings/tab:rules', $all_group_merged );
$settings_fields = [];
if(!empty($settings_all_fields)){
foreach($settings_all_fields as $key => $value){
$settings_fields[$key] = $value;
}
}
$fields['formipay_coupon_settings']['rules'] = array(
'name' => __('Rules', 'formipay'),
'fields' => $settings_fields
);
return $fields;
}
public function restriction_config($fields) {
// Group : Restrictions
$restriction_group = array(
'restriction_group' => array(
'type' => 'group_title',
'label' => __( 'Restrictions', 'formipay' ),
'group' => 'started'
),
'use_limit' => array(
'type' => 'number',
'label' => __( 'Usage Limit', 'formipay' ),
'description' => __( 'Leave it empty or 0 (zero) to set it as unlimited usage.', 'formipay' ),
'placeholder' => __( 'Set limit...', 'formipay' )
),
'date_limit' => array(
'type' => 'date',
'label' => __( 'Date Limit', 'formipay' ),
'description' => __( 'Chosen date is the last day the coupon can be used. Leave it empty tio set it as no-limit date.', 'formipay' )
),
'forms' => array(
'type' => 'autocomplete',
'post_type' => array('formipay-form'),
'label' => __( 'Forms', 'formipay' ),
'description' => __( 'Only selected form(s) can use the coupon. Leave empty to apply to all forms.', 'formipay' )
),
'products' => array(
'type' => 'autocomplete',
'post_type' => array('formipay-product'),
'label' => __( 'Products', 'formipay' ),
'description' => __( 'Only selected product(s) can use the coupon. Leave empty to apply to all products.', 'formipay' )
),
'users' => array(
'type' => 'autocomplete',
'object_type' => 'user',
'label' => __( 'Customers', 'formipay' ),
'description' => __( 'Only selected registered customer(s) can use this coupon. Leave empty to apply to all customers.', 'formipay' )
)
);
$restriction_group = apply_filters( 'formipay/coupon-settings/tab:restriction/group:restrictions', $restriction_group );
$last_restriction_group = array_key_last($restriction_group);
$restriction_group[$last_restriction_group]['group'] = 'ended';
$details_all_fields = apply_filters( 'formipay/coupon-settings/tab:restriction', $restriction_group );
$details_fields = [];
if(!empty($details_all_fields)){
foreach($details_all_fields as $key => $value){
$details_fields[$key] = $value;
}
}
$fields['formipay_coupon_settings']['restriction'] = array(
'name' => __('Restrictions', 'formipay'),
'fields' => $details_fields
);
return $fields;
}
public function add_static_element($elements) {
$elements[] = [
'id' => 'coupon_field',
'label' => __( 'Coupon Field', 'formipay' )
];
return $elements;
}
public function render_static_element($element_id) {
if($element_id == 'coupon_field'){
?>
<div class="formipay-field-group">
<div class="formipay-coupon-field-group">
<input type="text" name="coupon_code"
class="formipay-input formipay-code-input"
value="<?php echo
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
isset($_GET['coupon']) ? esc_attr( sanitize_text_field( wp_unslash($_GET['coupon']) ) ) : ''
?>" placeholder="<?php echo esc_attr__('Your coupon code', 'formipay'); ?>"
data-label="<?php echo esc_attr__('Coupon Code', 'formipay'); ?>">
<button type="button" data-text="<?php echo esc_attr__( 'Apply', 'formipay' ); ?>" data-checking="<?php echo esc_attr__( 'Checking...', 'formipay' ); ?>" id="apply_coupon_code"><?php echo esc_attr__( 'Apply', 'formipay' ); ?></button>
</div>
<div class="formipay-coupon-alert-message"></div>
</div>
<?php
}
}
public function check_coupon_code() {
check_ajax_referer( 'formipay-frontend-nonce', 'security' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$code = isset($_REQUEST['code']) ? sanitize_text_field( wp_unslash($_REQUEST['code']) ) : '';
$form_id = isset($_REQUEST['form']) ? intval( $_REQUEST['form'] ) : 0;
$check_code = formipay_get_coupon_id_by_code($code, $form_id);
if(false == $check_code){
wp_send_json_error( [
'message' => __( 'Coupon code is not valid', 'formipay' )
] );
}
wp_send_json_success();
}
public function order_details($details, $form_id, $order_data) {
if( isset($order_data['coupon_code']) && !empty($order_data['coupon_code']) ) {
$coupon_id = formipay_get_coupon_id_by_code( sanitize_text_field( $order_data['coupon_code'] ), $form_id );
if(false !== $coupon_id) {
$amount = floatval( formipay_get_post_meta( $coupon_id, 'amount' ) );
$discount = $amount;
$coupon_type = formipay_get_post_meta( $coupon_id, 'type' );
$qty = 1;
if( formipay_get_post_meta( $form_id, 'product_quantity_toggle' ) == 'on' ){
$qty = intval($order_data['qty']);
}
if( $coupon_type == 'percentage' ){
$product_price = floatval( formipay_get_post_meta( $form_id, 'product_price' ) );
$amount = $amount / 100;
$discount = $product_price * $amount;
}
$amount = $discount * $qty;
$amount = $amount * -1;
$details[] = [
'item' => esc_html( $order_data['coupon_code'] ),
'amount' => floatval($amount),
'subtotal' => floatval($amount),
'context' => 'sub'
];
}
}
return $details;
}
public function count_order_by_coupon_code($coupon_code) {
global $wpdb;
$table = $wpdb->prefix .'formipay_orders';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$data = $wpdb->get_results(
$wpdb->prepare(
"SELECT COUNT(id) as count FROM %i WHERE `items` LIKE %s", $table, '%' . $wpdb->esc_like($coupon_code) . '%'
)
);
return $data[0]->count;
}
public function add_discount_item($breakdown, $currency, $order_data) {
if( isset($order_data['form_data']['coupon_code']) && !empty($order_data['form_data']['coupon_code']) ) {
$form_id = $order_data['form_id'];
$coupon_id = formipay_get_coupon_id_by_code( sanitize_text_field( $order_data['form_data']['coupon_code'] ), $form_id );
if(false !== $coupon_id) {
$amount = floatval( formipay_get_post_meta( $coupon_id, 'amount' ) );
$discount = $amount;
$coupon_type = formipay_get_post_meta( $coupon_id, 'type' );
$qty = 1;
if( formipay_get_post_meta( $form_id, 'product_quantity_toggle' ) == 'on' ){
$qty = intval($order_data['form_data']['qty']);
}
if( $coupon_type == 'percentage' ){
$product_price = floatval( formipay_get_post_meta( $form_id, 'product_price' ) );
$amount = $amount / 100;
$discount = $product_price * $amount;
}
$breakdown['item_total']['value'] += $discount;
$breakdown['discount'] = array(
"currency_code" => $currency,
"value" => $discount
);
}
}
return $breakdown;
}
public function formipay_tabledata_coupons() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$args = [
'post_type' => 'formipay-coupon',
'posts_per_page' => -1,
'post_status' => array( 'pending', 'draft', 'publish' )
];
$get_total_coupons = get_posts($args);
$coupon_status = [
'all' => 0,
'active' => 0,
'inactive' => 0
];
if(!empty($get_total_coupons)){
foreach($get_total_coupons as $coupon){
$status = (formipay_get_post_meta($coupon->ID, 'active') == 'on') ? 'active' : 'inactive';
$coupon_status['all'] = intval($coupon_status['all']) + 1;
if($status == 'active'){
$coupon_status['active'] = intval($coupon_status['active']) + 1;
}elseif($status == 'inactive'){
$coupon_status['inactive'] = intval($coupon_status['inactive']) + 1;
}
}
}
if(isset($_REQUEST['limit'])){
$args['posts_per_page'] = intval($_REQUEST['limit']);
}
if(isset($_REQUEST['offset'])){
$args['offset'] = intval($_REQUEST['offset']);
}
if(!empty($_REQUEST['search'])){
$args['s'] = sanitize_text_field( wp_unslash($_REQUEST['search']) );
}
// Handle status filtering (active/inactive)
$status_filter = isset($_REQUEST['status']) ? sanitize_text_field($_REQUEST['status']) : 'all';
$get_coupons = get_posts($args);
$global_currencies = get_global_currency_array();
$coupons = [];
if(!empty($get_coupons)){
foreach($get_coupons as $key => $coupon){
$is_active = formipay_get_post_meta($coupon->ID, 'active') == 'on';
// Apply status filter
if ($status_filter !== 'all') {
if ($status_filter === 'active' && !$is_active) continue;
if ($status_filter === 'inactive' && $is_active) continue;
}
$date_limit = formipay_get_post_meta($coupon->ID, 'date_limit');
$date_limit_display = 'none';
if ($date_limit && $date_limit !== '') {
if (preg_match('/^\d{4}-\d{2}-\d{2}/', $date_limit)) {
$date_limit_display = $date_limit;
} elseif (is_numeric($date_limit)) {
$date_limit_display = formipay_date('Y-m-d', intval($date_limit) / 1000);
}
}
$type = formipay_get_post_meta($coupon->ID, 'type');
$amount_meta_key = "amount_$type";
if($type == 'fixed'){
$amount_value = [];
foreach($global_currencies as $currency){
$raw = $currency['currency'];
$decimal_digits = $currency['decimal_digits'];
$decimal_symbol = $currency['decimal_symbol'];
$thousand_separator = $currency['thousand_separator'];
$parse_currency = explode(':::', $raw);
$currency_code = $parse_currency[0];
$amount_in_currency = formipay_get_post_meta($coupon->ID, 'amount_fixed_'.$currency_code);
$amount_value[] = [
'raw' => $raw,
'amount' => number_format($amount_in_currency, $decimal_digits, $decimal_symbol, $thousand_separator),
'flag' => formipay_get_flag_by_currency($raw)
];
}
}else{
$amount_value = floatval(formipay_get_post_meta($coupon->ID, $amount_meta_key));
}
$coupons[] = [
'ID' => $coupon->ID,
'code' => get_the_title($coupon->ID),
'amount' => $amount_value,
'type' => ucfirst(formipay_get_post_meta($coupon->ID, 'type')),
'case_sensitive' => formipay_get_post_meta($coupon->ID, 'case_sensitive'),
'usage_count' => $this->count_order_by_coupon_code(get_the_title($coupon->ID)),
'usages' => $this->count_order_by_coupon_code(get_the_title($coupon->ID)),
'date_limit' => $date_limit_display,
'active' => $is_active ? 'on' : 'off',
'post_status' => $is_active ? 'active' : 'inactive',
'status' => $is_active ? 'active' : 'inactive'
];
}
}
$response = [
'results' => $coupons,
'total' => count($get_total_coupons), // Total number of forms
'posts_report' => array_map('intval', (array)$coupon_status)
];
wp_send_json($response);
}
public function formipay_coupon_get_products() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
if (!isset($_POST['search']) || empty($_POST['search'])) {
wp_send_json_error( __('No search term provided.', 'formipay') );
}
$search_term = sanitize_text_field( wp_unslash($_POST['search']) );
$query = get_posts([
'post_type' => 'formipay-form',
's' => $search_term,
'posts_per_page' => -1,
]);
$results = [];
if(!empty($query)){
foreach($query as $post){
$results[] = [
'value' => $post->ID,
'label' => $post->post_title,
];
}
}
wp_send_json($results);
}
public function formipay_create_coupon_post() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$code = isset($_REQUEST['title']) ? sanitize_text_field( wp_unslash($_REQUEST['title']) ) : '';
if( !empty($code) && '' !== $code ){
$post_id = wp_insert_post( [
'post_title' => $code,
'post_type' => 'formipay-coupon',
], true );
if(is_wp_error($post_id)){
wp_send_json_error( [
'message' => $post_id->get_error_message()
] );
}
update_post_meta($post_id, 'type', 'percentage');
update_post_meta($post_id, 'amount', 0);
wp_send_json_success( [
'edit_post_url' => admin_url('post.php?post='.$post_id.'&action=edit')
] );
}
wp_send_json_error( [
'message' => esc_html__( 'Coupon code is empty.', 'formipay' )
] );
}
public function formipay_delete_coupon() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
$delete = wp_delete_post($post_id, true);
if(is_wp_error( $delete )){
wp_send_json_error( [
'title' => esc_html__( 'Failed', 'formipay' ),
'message' => esc_html__( 'Failed to delete coupon. Please try again.', 'formipay' ),
'icon' => 'error'
] );
}
wp_send_json_success( [
'title' => esc_html__( 'Deleted', 'formipay' ),
'message' => esc_html__( 'Coupon is deleted permanently.', 'formipay' ),
'icon' => 'success'
] );
}
public function formipay_bulk_delete_coupon() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
if( empty($_REQUEST['ids']) ){
wp_send_json_error( [
'title' => esc_html__( 'Failed', 'formipay' ),
'message' => esc_html__( 'There is no coupon selected. Please try again.', 'formipay' ),
'icon' => 'error'
] );
}
$ids = isset($_REQUEST['ids']) ? $_REQUEST['ids'] : [];
$success = 0;
$failed = 0;
$report = __( 'Done.', 'formipay' );
if(!empty($ids)){
foreach($ids as $id){
$delete = wp_delete_post($id, true);
if(is_wp_error( $delete )){
$failed++;
}else{
$success++;
}
}
}
if($success > 0){
$report .= sprintf( __( ' Deleted %d coupon(s).', 'formipay' ), $success);
}
if($failed > 0){
$report .= sprintf( __( ' Failed %d coupon(s).', 'formipay' ), $failed);
}
wp_send_json_success( [
'title' => esc_html__( 'Done!', 'formipay' ),
'message' => $report,
'icon' => 'info'
] );
}
public function formipay_duplicate_coupon() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
$post = get_post($post_id);
if (!$post) {
wp_send_json_error( [
'title' => esc_html__('Failed', 'formipay'),
'message' => esc_html__( 'Coupon is not defined.', 'formipay' ),
'icon' => 'error'
] );
}
if (!in_array($post->post_type, ['formipay-coupon'])) {
wp_send_json_error( [
'title' => esc_html__('Failed', 'formipay'),
'message' => esc_html__( 'Wrong Post Type.', 'formipay' ),
'icon' => 'error'
] );
}
$duplicate_post = [
'post_title' => $post->post_title . ' ' . __('Copy', 'formipay'),
'post_content' => $post->post_content,
'post_status' => 'draft', // Set sebagai draft
'post_type' => $post->post_type,
'post_author' => $post->post_author,
];
// Masukkan duplikat ke database
$new_post_id = wp_insert_post($duplicate_post);
if (is_wp_error($new_post_id)) {
wp_send_json_error( [
'title' => esc_html__('Failed', 'formipay'),
'message' => esc_html__( 'Something happened. Please try again.', 'formipay' ),
'icon' => 'error'
] );
}
// Salin semua meta data
$meta_data = get_post_meta($post_id);
foreach ($meta_data as $key => $values) {
if($key !== 'forms'){
foreach ($values as $value) {
add_post_meta($new_post_id, $key, maybe_unserialize($value));
}
}
}
wp_send_json_success( [
'title' => esc_html__('Duplicated!', 'formipay'),
'message' => esc_html__( 'Coupon is successfully duplicated.', 'formipay' ),
'icon' => 'success'
] );
}
/**
* Get coupon data for React editor
*/
public function formipay_get_coupon() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
if ( empty($post_id) ) {
wp_send_json_error( [ 'message' => esc_html__( 'Coupon ID is required.', 'formipay' ) ] );
}
$post = get_post($post_id);
if ( ! $post || $post->post_type !== 'formipay-coupon' ) {
wp_send_json_error( [ 'message' => esc_html__( 'Coupon not found.', 'formipay' ) ] );
}
$global_currencies = get_global_currency_array();
// Build coupon data
$coupon_data = [
'ID' => $post_id,
'post_title' => $post->post_title,
'active' => formipay_get_post_meta($post_id, 'active'),
'type' => formipay_get_post_meta($post_id, 'type'),
'amount_percentage' => formipay_get_post_meta($post_id, 'amount_percentage'),
'case_sensitive' => formipay_get_post_meta($post_id, 'case_sensitive'),
'free_shipping' => formipay_get_post_meta($post_id, 'free_shipping'),
'quantity_active' => formipay_get_post_meta($post_id, 'quantity_active'),
'use_limit' => formipay_get_post_meta($post_id, 'use_limit'),
'date_limit' => formipay_get_post_meta($post_id, 'date_limit'),
'amounts_fixed' => [],
'max_amounts' => [],
'forms' => formipay_get_post_meta($post_id, 'forms') ?: [],
'products' => formipay_get_post_meta($post_id, 'products') ?: [],
'users' => formipay_get_post_meta($post_id, 'users') ?: [],
];
// Get fixed amounts for each currency
foreach ($global_currencies as $currency) {
$currency_raw = $currency['currency'];
$currency_parts = explode(':::', $currency_raw);
$currency_code = $currency_parts[0] ?? '';
$symbol = formipay_get_currency_data_by_value($currency_raw, 'symbol');
$amount_fixed = formipay_get_post_meta($post_id, 'amount_fixed_' . $symbol);
if ( $amount_fixed ) {
$coupon_data['amounts_fixed'][] = [
'currency' => $currency_code,
'symbol' => $symbol,
'amount' => $amount_fixed,
];
}
$max_amount = formipay_get_post_meta($post_id, 'max_amount_' . $symbol);
if ( $max_amount ) {
$coupon_data['max_amounts'][] = [
'currency' => $currency_code,
'symbol' => $symbol,
'amount' => $max_amount,
];
}
}
wp_send_json_success( $coupon_data );
}
/**
* Save coupon data from React editor
*/
public function formipay_save_coupon() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
$title = isset($_REQUEST['title']) ? sanitize_text_field(wp_unslash($_REQUEST['title'])) : '';
if ( empty($title) ) {
wp_send_json_error( [ 'message' => esc_html__( 'Coupon code is required.', 'formipay' ) ] );
}
$global_currencies = get_global_currency_array();
// Prepare post data
$post_data = [
'post_title' => $title,
'post_type' => 'formipay-coupon',
'post_status' => 'publish',
];
if ( $post_id > 0 ) {
$post_data['ID'] = $post_id;
$post_data['post_status'] = get_post_status($post_id);
$new_post_id = wp_update_post($post_data);
} else {
$new_post_id = wp_insert_post($post_data);
}
if ( is_wp_error($new_post_id) ) {
wp_send_json_error( [ 'message' => $new_post_id->get_error_message() ] );
}
// Save meta fields
$meta_fields = [
'active',
'type',
'amount_percentage',
'case_sensitive',
'free_shipping',
'quantity_active',
'use_limit',
'date_limit',
];
foreach ($meta_fields as $field) {
$value = isset($_REQUEST[$field]) ? wp_unslash($_REQUEST[$field]) : '';
update_post_meta($new_post_id, $field, $value);
}
// Save fixed amounts
foreach ($global_currencies as $currency) {
$symbol = formipay_get_currency_data_by_value($currency['currency'], 'symbol');
if ( isset($_REQUEST['amount_fixed_' . $symbol]) ) {
update_post_meta($new_post_id, 'amount_fixed_' . $symbol, wp_unslash($_REQUEST['amount_fixed_' . $symbol]));
}
if ( isset($_REQUEST['max_amount_' . $symbol]) ) {
update_post_meta($new_post_id, 'max_amount_' . $symbol, wp_unslash($_REQUEST['max_amount_' . $symbol]));
}
}
// Save relation fields
$relation_fields = ['forms', 'products', 'users'];
foreach ($relation_fields as $field) {
if ( isset($_REQUEST[$field]) ) {
$values = is_array($_REQUEST[$field]) ? array_map('intval', $_REQUEST[$field]) : [];
update_post_meta($new_post_id, $field, $values);
}
}
wp_send_json_success( [
'message' => esc_html__( 'Coupon saved successfully.', 'formipay' ),
'id' => $new_post_id,
] );
}
/**
* Add React metabox for coupon editor
*/
public function add_react_metabox() {
add_meta_box(
'formipay_coupon_settings',
__( 'Coupon Settings', 'formipay' ),
[$this, 'render_react_metabox'],
'formipay-coupon',
'normal',
'high'
);
}
/**
* Render React metabox container
*/
public function render_react_metabox($post) {
?>
<div
data-formipay-field-renderer="coupon"
data-post-id="<?php echo esc_attr($post->ID); ?>"
class="formipay-field-renderer-container"
>
<div class="formipay-loading">
<div class="formipay-spinner"></div>
</div>
</div>
<?php
}
/**
* Render React metabox template (hidden)
*/
public function render_react_metabox_template() {
global $post;
if (!$post || $post->post_type !== 'formipay-coupon') {
return;
}
// Add Toaster container for toast notifications
echo '<div id="formipay-toaster-container" class="formipay-design-system" style="position:fixed;bottom:20px;right:20px;z-index:99999;"></div>';
// Get field configuration for this post
$config = \Formipay\Admin\FieldConfigBridge::get_config_for_post($post->ID, $post->post_type);
// Pass config to JavaScript
?>
<script type="text/javascript">
window.formipayFieldConfig = <?php echo wp_json_encode($config); ?>;
</script>
<?php
// Keep the legacy global currencies for backward compatibility during transition
$global_currencies = get_global_currency_array();
// Fallback: if no multicurrencies configured, use default currency
if (empty($global_currencies)) {
$formipay_settings = get_option('formipay_settings');
$default_currency_raw = formipay_default_currency('raw');
if (!empty($default_currency_raw)) {
$global_currencies = [
[
'currency' => $default_currency_raw,
'decimal_digits' => isset($formipay_settings['default_currency_decimal_digits']) ? intval($formipay_settings['default_currency_decimal_digits']) : 2,
'decimal_symbol' => isset($formipay_settings['default_currency_decimal_symbol']) ? $formipay_settings['default_currency_decimal_symbol'] : '.',
'thousand_separator' => isset($formipay_settings['default_currency_thousand_separator']) ? $formipay_settings['default_currency_thousand_separator'] : ',',
]
];
}
}
?>
<script type="text/javascript">
window.formipayGlobalCurrencies = <?php echo wp_json_encode($global_currencies); ?>;
window.formipayGetFlag = function(currencyRaw) {
<?php
foreach ($global_currencies as $currency) {
echo 'if (currencyRaw === "' . esc_js($currency['currency']) . '") return "' . esc_url(formipay_get_flag_by_currency($currency['currency'])) . '";';
}
?>
return '';
};
</script>
<?php
}
/**
* Autocomplete search for relation fields
* Supports CPTs (post_type) and WP Users (object_type=user)
*/
public function formipay_autocomplete_search() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_type = isset($_REQUEST['post_type']) ? sanitize_text_field(wp_unslash($_REQUEST['post_type'])) : '';
$object_type = isset($_REQUEST['object_type']) ? sanitize_text_field(wp_unslash($_REQUEST['object_type'])) : '';
$search = isset($_REQUEST['search']) ? sanitize_text_field(wp_unslash($_REQUEST['search'])) : '';
$include = isset($_REQUEST['include']) ? array_map('intval', (array) $_REQUEST['include']) : [];
// Handle WP Users
if ($object_type === 'user') {
// Resolve labels for specific IDs (pre-selected items)
if (!empty($include)) {
$users = get_users(['include' => $include, 'fields' => ['ID', 'display_name', 'user_email']]);
$results = [];
if (!empty($users)) {
foreach ($users as $user) {
$results[] = [
'value' => $user->ID,
'label' => $user->display_name . ' (' . $user->user_email . ')',
];
}
}
wp_send_json_success($results);
return;
}
// Search by keyword
if (strlen($search) < 2) {
wp_send_json_error( [ 'message' => 'Invalid request' ] );
}
$users = get_users([
'search' => '*' . $search . '*',
'search_columns' => ['display_name', 'user_email', 'user_login'],
'number' => 20,
'fields' => ['ID', 'display_name', 'user_email'],
]);
$results = [];
if (!empty($users)) {
foreach ($users as $user) {
$results[] = [
'value' => $user->ID,
'label' => $user->display_name . ' (' . $user->user_email . ')',
];
}
}
wp_send_json_success($results);
return;
}
// Handle CPTs (posts)
if (empty($post_type)) {
wp_send_json_error( [ 'message' => 'Invalid request' ] );
}
// Resolve labels for specific IDs (pre-selected items)
if (!empty($include)) {
$query = get_posts([
'post_type' => $post_type,
'post__in' => $include,
'posts_per_page' => -1,
'post_status' => 'any',
]);
$results = [];
if (!empty($query)) {
foreach ($query as $post) {
$results[] = [
'value' => $post->ID,
'label' => $post->post_title,
];
}
}
wp_send_json_success($results);
return;
}
// Search by keyword
if (strlen($search) < 2) {
wp_send_json_error( [ 'message' => 'Invalid request' ] );
}
$query = get_posts([
'post_type' => $post_type,
's' => $search,
'posts_per_page' => 20,
'post_status' => ['publish', 'draft'],
]);
$results = [];
if (!empty($query)) {
foreach ($query as $post) {
$results[] = [
'value' => $post->ID,
'label' => $post->post_title,
];
}
}
wp_send_json_success($results);
}
/**
* Save coupon data via WordPress save_post hook
* Called when user clicks WordPress Update button
*/
public function save_coupon_on_post_update($post_id, $post) {
// Check if this is an autosave
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return $post_id;
}
// Check permissions
if (!current_user_can('manage_options')) {
return $post_id;
}
// Verify nonce
if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'update-post_' . $post_id)) {
return $post_id;
}
// Only save for our coupon post type
if ($post->post_type !== 'formipay-coupon') {
return $post_id;
}
// Get global currencies for currency field processing
$global_currencies = get_global_currency_array();
// Save basic meta fields
$meta_fields = [
'active',
'type',
'amount_percentage',
'case_sensitive',
'free_shipping',
'quantity_active',
'use_limit',
'date_limit',
];
foreach ($meta_fields as $field) {
if (isset($_POST[$field])) {
$value = wp_unslash($_POST[$field]);
update_post_meta($post_id, $field, $value);
}
}
// Save fixed amounts for each currency
foreach ($global_currencies as $currency) {
$symbol = formipay_get_currency_data_by_value($currency['currency'], 'symbol');
if (isset($_POST['amount_fixed_' . $symbol])) {
update_post_meta($post_id, 'amount_fixed_' . $symbol, wp_unslash($_POST['amount_fixed_' . $symbol]));
}
if (isset($_POST['max_amount_' . $symbol])) {
update_post_meta($post_id, 'max_amount_' . $symbol, wp_unslash($_POST['max_amount_' . $symbol]));
}
}
// Save relation fields
$relation_fields = ['forms', 'products', 'users'];
foreach ($relation_fields as $field) {
if (isset($_POST[$field])) {
$values = is_array($_POST[$field]) ? array_map('intval', $_POST[$field]) : [];
update_post_meta($post_id, $field, $values);
} else {
delete_post_meta($post_id, $field);
}
}
return $post_id;
}
}