Files
formipay/includes/Render.php
dwindown 35569923a5 docs: add comprehensive audit report and architectural recommendation
Checkpoint before implementation. Includes audit findings (FINDINGS.md),
architectural recommendation (RECOMMENDATION.md), and existing code changes
to Form, Order, Render, and form-action.js from recent development.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 17:00:47 +07:00

807 lines
46 KiB
PHP

<?php
namespace Formipay;
use Formipay\Traits\SingletonTrait;
use Formipay\Field;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;
class Render {
use SingletonTrait;
private $display_label;
private $show_label;
private $asterisk;
private static $form_ids = [];
/**
* Register hooks.
*/
private function __construct() {
add_shortcode('formipay', [$this, 'shortcode']);
add_action('wp_enqueue_scripts', [$this, 'public_enqueue_style'] );
}
/**
* Shortcode handler.
*
* @param array $atts
* @param string|null $content
* @return string|false
*/
public function shortcode($atts, $content = null) {
$atts = shortcode_atts(['form' => 0], $atts, 'formipay');
if ($atts['form'] == 0) {
return false;
}
$post_id = intval($atts['form']);
$currency = 'Rp. ';
if ($post_id > 0) {
$currency = formipay_post_currency($post_id);
self::$form_ids[] = $post_id;
}
$isDonation = formipay_is_donation($post_id);
$form_settings = get_post_meta($post_id, 'formipay_settings', true);
$point_symbol = formipay_get_post_meta($post_id, 'multistep_point_symbol');
$point_icons = formipay_get_post_meta($post_id, 'multistep_point_icons');
$page_breaks = [];
if(!empty($form_settings['fields'])){
foreach($form_settings['fields'] as $field){
if($field['field_type'] == 'page_break'){
if($point_symbol == 'icon'){
$icons = json_decode($point_icons, true);
foreach($icons as $icon){
if($icon['field_id'] == $field['field_id']){
$field['icon'] = $icon['script'];
}
}
}
$page_breaks[] = $field;
}
}
}
ob_start();
$timezone = get_option('timezone_string', 'Asia/Jakarta');
$require_login = formipay_get_post_meta($post_id, 'require_login') === 'on';
$scheduled = formipay_get_post_meta($post_id, 'schedule_toggle') === 'on';
$render = true;
if ($require_login && !is_user_logged_in()) {
$render = false;
echo '<div class="formipay-message">' . wp_kses_post(formipay_get_post_meta($post_id, 'require_login_message')) . '</div>';
} elseif ($scheduled) {
$date_range = explode(',', formipay_get_post_meta($post_id, 'daterange'));
$date_from = new DateTime($date_range[0], new DateTimeZone($timezone));
$date_to = new DateTime($date_range[1], new DateTimeZone($timezone));
$now = new DateTime('now', new DateTimeZone($timezone));
if ($date_from > $now) {
$render = false;
?>
<div class="formipay-message">
<?php echo wp_kses_post(formipay_get_post_meta($post_id, 'waiting_message')); ?>
</div>
<?php
} elseif ($now > $date_to) {
$render = false;
?>
<div class="formipay-message">
<?php echo wp_kses_post(formipay_get_post_meta($post_id, 'expired_message')); ?>
</div>
<?php
}
}
if ($render && $form_settings && is_array($form_settings['fields']) && count($form_settings['fields']) > 0) {
$this->public_enqueue_scripts();
$button_background_color = json_decode(formipay_get_post_meta($post_id, 'button_bg_color'), true );
$button_text_color = json_decode(formipay_get_post_meta($post_id, 'button_text_color'), true );
$button_border_color = json_decode( formipay_get_post_meta($post_id, 'button_border_color'), true );
?>
<div class="formipay-form-wrapper">
<style>
body {
--formipay-button-submit-bg-color: <?php echo esc_html($button_background_color['regular'] ?? '#000000'); ?>;
--formipay-button-submit-bg-color-hover: <?php echo esc_html($button_background_color['hover'] ?? '#ffffff'); ?>;
--formipay-button-submit-bg-color-active: <?php echo esc_html($button_background_color['active'] ?? '#ffffff'); ?>;
--formipay-button-submit-text-color: <?php echo esc_html($button_text_color['regular'] ?? '#ffffff'); ?>;
--formipay-button-submit-text-color-hover: <?php echo esc_html($button_text_color['hover'] ?? '#000000') ?>;
--formipay-button-submit-text-color-active: <?php echo esc_html($button_text_color['active'] ?? '#000000'); ?>;
--formipay-button-submit-border-color: <?php echo esc_html($button_border_color['regular'] ?? '#000000'); ?>;
--formipay-button-submit-border-color-hover: <?php echo esc_html($button_border_color['hover'] ?? '#ffffff'); ?>;
--formipay-button-submit-border-color-active: <?php echo esc_html($button_border_color['active'] ?? '#ffffff'); ?>;
--formipay-required-field-sign-color: <?php echo esc_html(formipay_get_post_meta($post_id, 'required_field_color')); ?>;
--formipay-payment-desktop-columns: <?php echo intval(formipay_get_post_meta($post_id, 'payment_desktop_columns') ?? 4); ?>;
--formipay-payment-tablet-columns: <?php echo intval(formipay_get_post_meta($post_id, 'payment_tablet_columns') ?? 3) ?>;
--formipay-payment-mobile-columns: <?php echo intval(formipay_get_post_meta($post_id, 'payment_mobile_columns') ?? 2) ?>;
<?php
if(formipay_isPopup($post_id)){
$popup_button_background_color = json_decode(formipay_get_post_meta($post_id, 'popup_button_bg_color'), true );
$popup_button_text_color = json_decode(formipay_get_post_meta($post_id, 'popup_button_text_color'), true );
$popup_button_border_color = json_decode( formipay_get_post_meta($post_id, 'popup_button_border_color'), true );
?>
--formipay-popup-button-bg-color: <?php echo esc_html($popup_button_background_color['regular'] ?? '#000000'); ?>;
--formipay-popup-button-bg-color-hover: <?php echo esc_html($popup_button_background_color['hover'] ?? '#ffffff'); ?>;
--formipay-popup-button-bg-color-active: <?php echo esc_html($popup_button_background_color['active'] ?? '#ffffff'); ?>;
--formipay-popup-button-text-color: <?php echo esc_html($popup_button_text_color['regular'] ?? '#ffffff'); ?>;
--formipay-popup-button-text-color-hover: <?php echo esc_html($popup_button_text_color['hover'] ?? '#000000'); ?>;
--formipay-popup-button-text-color-active: <?php echo esc_html($popup_button_text_color['active'] ?? '#000000'); ?>;
--formipay-popup-button-border-color: <?php echo esc_html($popup_button_border_color['regular'] ?? '#000000'); ?>;
--formipay-popup-button-border-color-hover: <?php echo esc_html($popup_button_border_color['hover'] ?? '#ffffff'); ?>;
--formipay-popup-button-border-color-active: <?php echo esc_html($popup_button_border_color['active'] ?? '#ffffff'); ?>;
--formipay-popup-backdrop-color: <?php echo esc_html(formipay_get_post_meta($post_id, 'popup_content_wrapper_backdrop_color') ?? 'rgba(0,0,0,0.75)'); ?>;
--formipay-popup-wrapper-width: <?php echo esc_html(formipay_get_post_meta($post_id, 'popup_content_wrapper_width')); ?>px;
--formipay-popup-button-width: <?php echo esc_html(formipay_get_post_meta($post_id, 'popup_button_width') == 'fullwidth' ? '100%' : 'fit-content'); ?>;
<?php
}
if(count($page_breaks) > 0 && !is_admin()){
?>
--formipay-page-break-count: <?php echo count($page_breaks) + 1 ?>;
<?php
}
switch (formipay_get_post_meta($post_id, 'multistep_point_shape')) {
case 'rectangle':
?>
--formipay-step-shape-radius: 0%;
<?php
break;
case 'rounded':
?>
--formipay-step-shape-radius: 25%;
<?php
break;
default:
?>
--formipay-step-shape-radius: 50%;
<?php
break;
}
switch (formipay_get_post_meta($post_id, 'multistep_btn_size')) {
case 'smaller_than_submit':
?>
--formipay-step-button-size: .5em 1em;
--formipay-step-button-text-size: smaller;
--formipay-step-button-height: 32px;
<?php
break;
case 'larger_than_submit':
?>
--formipay-step-button-size: 1em 2em;
--formipay-step-button-text-size: larger;
--formipay-step-button-height: 48px;
<?php
break;
default:
?>
--formipay-step-button-size: .75em 1.5em;
--formipay-step-button-text-size: inherit;
--formipay-step-button-height: 40px;
<?php
break;
}
?>
}
</style>
<?php if(formipay_isPopup($post_id)) { ?>
<div class="formipay-popup modal" id="formipay-popup-<?php echo intval($post_id); ?>">
<?php } ?>
<form class="formipay-form" data-form-id="<?php echo intval($post_id); ?>" id="formipay-form-<?php echo intval($post_id); ?>">
<input type="hidden" id="product_name" value="<?php echo esc_attr(get_the_title($post_id)); ?>">
<input type="hidden" id="product_price" value="<?php echo esc_attr(formipay_get_post_meta($post_id, 'product_price')); ?>">
<?php if(formipay_isPopup($post_id) && !empty(formipay_get_post_meta($post_id, 'popup_content_wrapper_title'))) { ?>
<h4 class="formipay-popup-title"><?php echo esc_html(formipay_get_post_meta($post_id, 'popup_content_wrapper_title')); ?></h4>
<?php } ?>
<div class="formipay-form-body formipay-<?php echo (count($page_breaks) > 0 && !is_admin()) ? '1' : esc_attr(formipay_get_post_meta($post_id, 'form_layout_columns')); ?>-cols">
<div class="form-fields-wrapper">
<?php if(!empty(formipay_get_post_meta($post_id, 'product_description'))) { ?>
<div class="formipay-description">
<?php echo wp_kses_post(formipay_get_post_meta($post_id, 'product_description')); ?>
</div>
<?php } ?>
<?php
$label_show = formipay_get_post_meta($post_id, 'label_visibility') !== 'hide';
$this->display_label = !$label_show ? ' formipay-hidden-element' : '';
$this->asterisk = formipay_get_post_meta($post_id, 'required_field_sign') === 'text' ? formipay_get_post_meta($post_id, 'required_text') : '(*)';
if(count($page_breaks) > 0 && !is_admin()){
$this->render_top_pagination_process($page_breaks, $point_symbol);
}
foreach ($form_settings['fields'] as $field_id => $field) {
$field_id = str_replace('_config', '', $field_id);
$field['field_id'] = $field_id;
$field['placeholder'] = formipay_get_post_meta($post_id, 'field_placeholder') === 'label'
? ($field['label'] ?? '')
: ($field['placeholder'] ?? '');
$field['calculable'] = in_array($field['field_type'], ['hidden', 'select', 'checkbox', 'radio']) ? ' formipay-input-calculable' : '';
$field['calc_value'] = $field['calc_value'] ?? 0;
$field['calc'] = in_array($field['field_type'], ['hidden']) ? 'data-calc-value="' . $field['calc_value'] . '"' : '';
$this->render_field($post_id, $field_id, $field);
}
?>
<?php
if(count($page_breaks) > 0 && !is_admin()){
?>
<div class="formipay-field-group formipay-page-break formipay-page-break-payment">
<div class="formipay-page-break-information">
<label for="<?php echo esc_attr($field_id); ?>" class="formipay-label divider-label <?php echo esc_attr($display_label); ?>">
<?php echo esc_html__('Payment', 'formipay'); ?>
</label>
<span class="divider-line"></span>
</div>
</div>
<?php
}
$this->render_bottom_pagination_process($post_id, $page_breaks);
?>
</div>
<div class="formipay-static-elements">
<div class="result-wrapper">
<input class="formipay-meta-input" type="hidden" name="user_id" value="<?php echo is_user_logged_in() ? esc_attr( absint( get_current_user_id() ) ) : ''; ?>">
<input class="formipay-meta-input" type="hidden" name="session_id" value="<?php echo esc_attr( sanitize_text_field( uniqid('', true) ) ); ?>">
<input class="formipay-meta-input" type="hidden" name="referrer" value="<?php echo isset($_SERVER['HTTP_REFERER']) ? esc_attr( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) ) : ''; ?>">
<input class="formipay-meta-input" type="hidden" name="page_url" value="<?php echo isset($_SERVER['REQUEST_URI']) ? esc_attr( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) : ''; ?>">
<input class="formipay-meta-input" type="hidden" name="timestamp" value="<?php echo esc_attr( absint( time() ) ); ?>">
<input class="formipay-meta-input" type="hidden" name="utm_source" value="<?php echo
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
isset($_GET['utm_source']) ? esc_attr( sanitize_text_field( wp_unslash( $_GET['utm_source'] ) ) ) : '';
?>">
<input class="formipay-meta-input" type="hidden" name="utm_medium" value="<?php echo
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
isset($_GET['utm_medium']) ? esc_attr( sanitize_text_field( wp_unslash( $_GET['utm_medium'] ) ) ) : '';
?>">
<input class="formipay-meta-input" type="hidden" name="utm_campaign" value="<?php echo
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
isset($_GET['utm_campaign']) ? esc_attr( sanitize_text_field( wp_unslash( $_GET['utm_campaign'] ) ) ) : '';
?>">
<input class="formipay-meta-input" type="hidden" name="ip_address" value="<?php echo
isset($_SERVER['REMOTE_ADDR']) ? esc_attr( sanitize_text_field( wp_unslash($_SERVER['REMOTE_ADDR']) ) ) : '';
?>">
<input class="formipay-meta-input" type="hidden" name="user_agent" value="<?php echo
isset($_SERVER['HTTP_USER_AGENT']) ? esc_attr( sanitize_text_field( wp_unslash($_SERVER['HTTP_USER_AGENT']) ) ) : '';
?>">
<input class="formipay-meta-input" type="hidden" name="device_type" value="<?php echo esc_attr( ( strpos( sanitize_text_field( wp_unslash($_SERVER['HTTP_USER_AGENT']) ), 'Mobile' ) !== false ) ? 'Mobile' : 'Desktop' ); ?>">
<?php
$static_element_sort = json_decode(formipay_get_post_meta($post_id, 'form_static_elements_sort'), true);
$static_element_active = $static_element_sort[1]['options'];
if(!empty($static_element_active)){
foreach($static_element_active as $element){
switch ($element['id']) {
case 'payment_methods':
$this->render_payment_options($post_id);
break;
case 'coupon_fields':
?>
<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="Your coupon code"
data-label="Coupon Code">
<button type="button" data-text="<?php echo esc_html__( 'Apply', 'formipay' ); ?>" data-checking="<?php echo esc_html__( 'Checking...', 'formipay' ); ?>" id="apply_coupon_code"><?php echo esc_html__( 'Apply', 'formipay' ); ?></button>
</div>
<div class="formipay-coupon-alert-message"></div>
</div>
<?php
break;
case 'order_review':
$cart = $this->build_cart_from_meta($post_id);
?>
<div class="form-calculation form-calculate-<?php echo esc_attr($post_id); ?>">
<h4><?php echo esc_html(formipay_get_post_meta($post_id, 'order_review_title')); ?></h4>
<table id="formipay-review-order">
<tbody>
<?php foreach ($cart['lines'] as $line): ?>
<tr class="formipay-item-row <?php echo $line['type']==='product' ? 'formipay-product-row' : 'formipay-static-item-row'; ?>">
<th>
<?php echo esc_html($line['name']); ?>
<input type="hidden" class="formipay-qty-input" value="<?php echo (int) $line['qty']; ?>">
<?php if ((int) $line['qty'] > 1): ?>
<div class="formipay-qty-note">x<?php echo (int) $line['qty']; ?></div>
<?php endif; ?>
</th>
<td class="product_price"><?php echo esc_html(formipay_price_format((float) $line['total'], $post_id)); ?></td>
</tr>
<?php endforeach; ?>
<tr class="formipay-total-row">
<th><?php echo esc_html__( 'Subtotal', 'formipay' ); ?></th>
<td><?php echo esc_html(formipay_price_format((float) $cart['subtotal'], $post_id)); ?></td>
</tr>
<tr class="formipay-grand-total-row">
<th><?php echo esc_html__( 'Total', 'formipay' ); ?></th>
<td class="grand_total"><?php echo esc_html(formipay_price_format((float) $cart['grand'], $post_id)); ?></td>
</tr>
</tbody>
</table>
</div>
<?php
break;
case 'submit_button':
$cart = $cart ?? $this->build_cart_from_meta($post_id);
$grand_display = formipay_price_format((float) $cart['grand'], $post_id);
?>
<button type="submit" class="formipay-submit-button"
data-button-text="<?php echo esc_attr(formipay_get_post_meta($post_id, 'button_text')); ?>"
style="width: <?php echo formipay_get_post_meta($post_id, 'button_width') == 'fit-content' ? 'fit-content' : '100%' ?>;
margin-left: <?php echo formipay_get_post_meta($post_id, 'button_position') !== 'left' ? 'auto' : 'unset' ?>;
margin-right: <?php echo formipay_get_post_meta($post_id, 'button_position') !== 'right' ? 'auto' : 'unset' ?>;">
<?php echo esc_html(formipay_get_post_meta($post_id, 'button_text')); ?> - <?php echo esc_html($grand_display); ?>
</button>
<?php
break;
case 'submit_response_notice':
?>
<div class="submit-response formipay-message" style="display: none;"></div>
<?php
break;
default:
do_action('formipay/render/static-element', $element['id']);
break;
}
}
}
?>
</div>
</div>
</div>
</form>
<?php if(formipay_isPopup($post_id) && false == boolval(formipay_get_post_meta($post_id, 'popup_click_selector'))) { ?>
</div>
<div class="formipay-popup-trigger">
<a data-modal="#formipay-popup-<?php echo intval($post_id); ?>"
class="formipay-open-popup-button"
style="width: <?php echo formipay_get_post_meta($post_id, 'popup_button_width') == 'fit-content' ? 'fit-content' : '100%' ?>;
margin-left: <?php echo formipay_get_post_meta($post_id, 'popup_button_alignment') !== 'left' ? 'auto' : 'unset' ?>;
margin-right: <?php echo formipay_get_post_meta($post_id, 'popup_button_alignment') !== 'right' ? 'auto' : 'unset' ?>;">
<?php echo esc_html(formipay_get_post_meta($post_id, 'popup_button_text')); ?>
</a>
</div>
<?php } ?>
</div>
<?php
}
return ob_get_clean();
}
private function render_field($post_id, $field_id, $field) {
$type = $field['field_type'];
// Normalize method name for Field class
$method = "render_{$type}_field";
if (method_exists(Field::get_instance(), $method)) {
echo Field::get_instance()->$method($field);
} else {
// Optionally handle unknown field types
do_action('formipay/render/custom-field-type', $type, $field);
}
}
/**
* Build a simple cart from static products and static items meta
*/
private function build_cart_from_meta($post_id){
$currency_full = formipay_post_currency($post_id); // e.g., "IDR:::Indonesian rupiah:::Rp"
$parts = explode(':::', (string) $currency_full);
$currency_code = $parts[0] ?? 'IDR';
$lines = [];
$subtotal = 0.0;
// 1) Static PRODUCTS (IDs stored as comma-separated string)
$ids_raw = (string) formipay_get_post_meta($post_id, 'static_products');
$ids = array_filter(array_map('absint', explode(',', $ids_raw)));
foreach ($ids as $pid) {
$pname = get_the_title($pid);
$price = $this->get_product_price_for_currency($pid, $currency_code);
$qty = 1; // non-donation, no in-cart items => default 1
$line_total = (float) $price * $qty;
$subtotal += $line_total;
$lines[] = [
'type' => 'product',
'id' => $pid,
'name' => $pname,
'qty' => $qty,
'unit' => (float) $price,
'total'=> $line_total,
];
}
// 2) Static ITEMs (JSON array with currency-specific amounts)
$raw = formipay_get_post_meta($post_id, 'static_items');
if ($raw) {
$items = json_decode((string) $raw, true) ?: [];
foreach ($items as $it) {
$label = $it['label'] ?? 'Item';
$qty = (int) ($it['quantity'] ?? 1);
$k = 'amount_' . $currency_code;
$amt = (float) ($it[$k] ?? 0);
$line_total = $amt * $qty;
$subtotal += $line_total;
$lines[] = [
'type' => 'item',
'id' => null,
'name' => $label,
'qty' => $qty,
'unit' => $amt,
'total'=> $line_total,
];
}
}
// Placeholders for future rules
$discount = 0.0;
$shipping = 0.0;
$tax = 0.0;
$grand = max($subtotal - $discount + $shipping + $tax, 0);
return compact('currency_code','lines','subtotal','discount','shipping','tax','grand');
}
/**
* Resolve a product's price for a given currency code using common meta keys.
* Fallback order: product_price_{CODE} → price_{CODE} → product_price → price → 0
*/
private function get_product_price_for_currency($product_id, $currency_code){
$cands = [
'product_price_' . $currency_code,
'price_' . $currency_code,
'product_price',
'price',
];
foreach ($cands as $key) {
$val = formipay_get_post_meta($product_id, $key);
if ($val !== '' && $val !== null) {
return (float) $val;
}
}
return 0.0;
}
/**
* Render payment options
*/
private function render_payment_options($post_id) {
$payments = apply_filters( 'formipay/frontend/payment-list', [], $post_id );
?>
<div class="formipay-field-group">
<label class="formipay-label"><?php echo esc_html(formipay_get_post_meta($post_id, 'payment_section_title')); ?></label>
<div class="formipay-payments">
<?php
$unique_code = wp_rand(1, 999);
if(!empty($payments)){
foreach ($payments as $value => $payment) {
$channel = explode(':::', $value);
$gateway = $channel[0];
$payment_non_label_class = formipay_get_post_meta($post_id, 'payment_label') == 'on' ? '' : 'payment-logo-only';
$logo = '';
// Check if payment logo is an attachment ID or URL
if (!empty($payment[$gateway.'_logo'])) {
if (is_numeric($payment[$gateway.'_logo'])) {
// Media library image
$logo = wp_get_attachment_image(
intval($payment[$gateway.'_logo']),
['100', '40'],
false,
[
'class' => 'formipay-payment-logo formipay-'.$gateway.'-logo '.$payment_non_label_class,
'loading' => 'lazy'
]
);
} else {
// Static plugin image (add PHPCS ignore)
$logo_url = $payment[$gateway.'_logo'];
// phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
$logo = '<img src="' . esc_url($logo_url) . '"
width="100"
height="40"
class="formipay-payment-logo formipay-'.$gateway.'-logo '.$payment_non_label_class.'"
alt="' . esc_attr($payment[$gateway.'_name']) . '"
loading="lazy">';
}
}
?>
<div class="formipay-payment-option-group">
<label for="<?php echo esc_attr($value) ?>" class="formipay-label">
<input type="radio"
name="payment"
class="formipay-input formipay-input-calculable formipay-payment-channel"
id="<?php echo esc_attr($value); ?>"
value="<?php echo esc_attr($value); ?>"
required>
<?php echo $logo; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if(formipay_get_post_meta($post_id, 'payment_label') == 'on') { ?>
<span class="formipay-payment-name formipay-<?php echo esc_attr($gateway) ?>-name">
<?php echo esc_html($payment[$gateway.'_name']); ?>
</span>
<?php } ?>
</label>
</div>
<?php
}
}
?>
</div>
</div>
<?php
}
/**
* Render progess pages (on top)
*/
private function render_top_pagination_process($page_breaks, $point_symbol){
$payment_symbol = count($page_breaks) + 1;
if($point_symbol == 'icon'){
$payment_symbol = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19H6a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v4.5M3 10h18m-5 9h6m-3-3l3 3l-3 3M7.005 15h.005M11 15h2" />
</svg>';
}
if(!empty($page_breaks)){
?>
<div class="formipay-top-pagination">
<?php
foreach($page_breaks as $index => $page){
$index = $index + 1;
$symbol = $index;
if($point_symbol == 'icon'){
$symbol = '<span class="pagination-icon">'.$page['icon'].'</span>';
}
?>
<div class="formipay-progress" data-page-number="<?php echo intval($index) ?>">
<div class="formipay-index-wrapper">
<span class="formipay-page-index"><?php echo wp_kses_post($symbol) ?></span>
<span class="formipay-page-label"><?php echo esc_html($page['label']) ?></span>
</div>
</div>
<?php
}
?>
<div class="formipay-progress" data-page-number="<?php echo count($page_breaks) + 1 ?>">
<div class="formipay-index-wrapper">
<span class="formipay-page-index"><?php echo wp_kses_post($payment_symbol); ?></span>
<span class="formipay-page-label"><?php echo esc_html__( 'Payment', 'formipay' ); ?></span>
</div>
</div>
</div>
<?php
}
}
private function render_bottom_pagination_process($post_id, $page_breaks) {
if(!empty($page_breaks)){
?>
<div class="formipay-bottom-pagination">
<button type="button" class="formipay-page-break-prev-button" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h6m3 0h1.5m3 0h.5M5 12l4 4m-4-4l4-4" />
</svg>
<?php echo esc_html(formipay_get_post_meta($post_id, 'multistep_btn_prev_text')); ?>
</button>
<button type="button" class="formipay-page-break-next-button">
<?php echo esc_html(formipay_get_post_meta($post_id, 'multistep_btn_next_text')); ?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h.5m3 0H10m3 0h6m-4 4l4-4m-4-4l4 4" />
</svg>
</button>
</div>
<?php
}
}
/**
* Enqueue public scripts and styles.
*/
public function public_enqueue_style() {
wp_enqueue_style( 'sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.css', [], '11.14.5', 'all');
wp_enqueue_style( 'jquery-modal', FORMIPAY_URL . 'vendor/jQueryModal/jquery.modal.min.css', [], '0.9.1', 'all');
wp_enqueue_style( 'choices', FORMIPAY_URL . 'vendor/ChoicesJS/choices.min.css', [], FORMIPAY_VERSION, 'all' );
wp_enqueue_style( 'formipay-form', FORMIPAY_URL . 'public/assets/css/form-style.css', [], FORMIPAY_VERSION, 'all');
wp_enqueue_style( 'formipay-popup', FORMIPAY_URL . 'public/assets/css/popup-style.css', [], FORMIPAY_VERSION, 'all');
}
public function public_enqueue_scripts() {
wp_enqueue_script( 'jquery-blockui', FORMIPAY_URL . 'vendor/jQuery-UI/jquery.blockUI.min.js', ['jquery'], FORMIPAY_VERSION, true);
wp_enqueue_script( 'jquery-clipboard', FORMIPAY_URL . 'vendor/ClipboardJS/clipboard.min.js', ['jquery'], '2.0.11', true);
wp_enqueue_script( 'sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.js', ['jquery'], '11.14.5', true);
wp_enqueue_script( 'jquery-modal', FORMIPAY_URL . 'vendor/jQueryModal/jquery.modal.min.js', ['jquery'], '0.9.1', true);
wp_enqueue_script( 'choices', FORMIPAY_URL . 'vendor/ChoicesJS/choices.min.js', [], FORMIPAY_VERSION, true );
wp_enqueue_script( 'formipay-popup', FORMIPAY_URL . 'public/assets/js/popup-action.js', ['jquery', 'choices'], FORMIPAY_VERSION, true);
wp_enqueue_script( 'formipay-form', FORMIPAY_URL . 'public/assets/js/form-action.js', ['jquery', 'choices'], FORMIPAY_VERSION, true);
// Localize data for all forms
$form_data = [
'ajax_url' => admin_url('admin-ajax.php'),
'frontend_nonce' => wp_create_nonce('formipay-frontend-nonce'),
'nonce' => wp_create_nonce('formipay_order_submit'),
'forms' => $this->get_form_data()
];
wp_localize_script('formipay-form', 'formipay_form', $form_data);
}
/**
* Resolve currency UI/config for a given 3-letter currency code from wp_options.
*/
private function resolve_currency_config($currency_code){
$opts = get_option('formipay_settings', []);
// Defaults from global default_* settings
$default_full = isset($opts['default_currency']) ? (string)$opts['default_currency'] : 'IDR:::Indonesian rupiah:::Rp';
$def_parts = explode(':::', $default_full);
$def_symbol = $def_parts[2] ?? '';
$cfg = [
'currency' => $def_symbol,
'decimal_digits' => isset($opts['default_currency_decimal_digits']) ? (int)$opts['default_currency_decimal_digits'] : 2,
'decimal_symbol' => isset($opts['default_currency_decimal_symbol']) ? (string)$opts['default_currency_decimal_symbol'] : '.',
'thousand_separator' => isset($opts['default_currency_thousand_separator']) ? (string)$opts['default_currency_thousand_separator'] : ',',
];
if (!empty($opts['multicurrencies']) && is_array($opts['multicurrencies'])) {
foreach ($opts['multicurrencies'] as $mc) {
if (empty($mc['currency'])) continue;
$parts = explode(':::', (string)$mc['currency']);
$code = $parts[0] ?? '';
if (strtoupper($code) !== strtoupper($currency_code)) continue;
$symbol = $parts[2] ?? '';
if ($symbol !== '') $cfg['currency'] = $symbol;
if ($mc['decimal_digits'] !== '') $cfg['decimal_digits'] = (int)$mc['decimal_digits'];
if ($mc['decimal_symbol'] !== '') $cfg['decimal_symbol'] = (string)$mc['decimal_symbol'];
if ($mc['thousand_separator'] !== '') $cfg['thousand_separator'] = (string)$mc['thousand_separator'];
break;
}
}
return $cfg;
}
/**
* Build the allowed currency list for a form, including UI config per currency.
* Uses form meta 'allowed_currencies' (JSON array of "CODE:::Title:::Symbol")
* and 'default_currencies' (single string) to set default.
*/
private function resolve_allowed_currencies($post_id){
// Allowed on the form
$raw = formipay_get_post_meta($post_id, 'allowed_currencies');
$allowed = [];
if (!empty($raw)) {
$arr = json_decode((string)$raw, true);
if (is_array($arr)) $allowed = $arr;
}
// Fallback to all global currencies if the form has none
if (empty($allowed)) {
$opts = get_option('formipay_settings', []);
if (!empty($opts['multicurrencies']) && is_array($opts['multicurrencies'])) {
foreach ($opts['multicurrencies'] as $mc) {
if (!empty($mc['currency'])) $allowed[] = (string)$mc['currency'];
}
}
}
// Default for the form
$default_full = formipay_get_post_meta($post_id, 'default_currencies');
if (empty($default_full)) {
$opts = get_option('formipay_settings', []);
$default_full = $opts['default_currency'] ?? 'IDR:::Indonesian rupiah:::Rp';
}
$def_parts = explode(':::', (string)$default_full);
$default_code = $def_parts[0] ?? 'IDR';
// Compose structured list
$list = [];
foreach ($allowed as $cur_full) {
$parts = explode(':::', (string)$cur_full);
$code = $parts[0] ?? '';
$title = $parts[1] ?? '';
$symbol = $parts[2] ?? '';
if (!$code) continue;
$cfg = $this->resolve_currency_config($code);
if ($symbol !== '') $cfg['currency'] = $symbol; // prefer explicit symbol in the tuple
$list[] = [
'code' => $code,
'title' => $title,
'symbol' => $cfg['currency'],
'decimal_digits' => (int)$cfg['decimal_digits'],
'decimal_symbol' => (string)$cfg['decimal_symbol'],
'thousand_separator' => (string)$cfg['thousand_separator'],
];
}
return [
'default_code' => $default_code,
'list' => $list,
];
}
private function get_form_data(){
$form_data = [];
foreach (array_unique(self::$form_ids) as $post_id) {
$allowed_currency_pack = $this->resolve_allowed_currencies($post_id);
$currency_code = $allowed_currency_pack['default_code'];
$currency_cfg = $this->resolve_currency_config($currency_code);
$form_data[$post_id] = [
'form_id' => $post_id,
'currency' => formipay_post_currency($post_id),
'currency_code' => $currency_code, // active on load
'currency' => $currency_cfg['currency'],
'decimal_digits' => $currency_cfg['decimal_digits'],
'decimal_symbol' => $currency_cfg['decimal_symbol'],
'thousand_separator' => $currency_cfg['thousand_separator'],
'allowed_currency_pack' => $allowed_currency_pack,
'buyer_phone_field' => formipay_get_post_meta($post_id, 'buyer_phone'),
'buyer_country_field' => formipay_get_post_meta($post_id, 'buyer_country'),
'buyer_phone_allow' => (bool) formipay_get_post_meta($post_id, 'buyer_allow_choose_country_code'),
'buyer_phone_country_code' => formipay_get_post_meta($post_id, 'buyer_phone_country_code'),
'notice_empty_text_message' => formipay_get_post_meta($post_id, 'empty_required_text_field'),
'notice_empty_select_message' => formipay_get_post_meta($post_id, 'empty_required_select_field'),
'notice_empty_agreement_message' => formipay_get_post_meta($post_id, 'empty_required_agreement_field'),
'quantity_toggle' => formipay_get_post_meta($post_id, 'product_quantity_toggle'),
'quantity_step' => formipay_get_post_meta($post_id, 'product_quantity_range'),
'quantity_min' => formipay_get_post_meta($post_id, 'product_minimum_purchase'),
'quantity_stock' => formipay_get_post_meta($post_id, 'product_stock'),
'button_text' => formipay_get_post_meta($post_id, 'button_text'),
'button_processing_text' => formipay_get_post_meta($post_id, 'button_processing_text'),
'isPopup' => formipay_isPopup($post_id),
'trigger_selector' => formipay_get_post_meta($post_id, 'popup_click_selector') ?
formipay_get_post_meta($post_id, 'popup_trigger_selector') :
'.formipay-open-popup-button',
'modal_selector' => '#formipay-popup-' . $post_id,
'static_products' => array_filter(array_map('absint', explode(',', (string) formipay_get_post_meta($post_id, 'static_products')))),
'static_items' => json_decode((string) formipay_get_post_meta($post_id, 'static_items'), true) ?: [],
'currency_code' => (function($c){ $p = explode(':::', (string)$c); return $p[0] ?? 'IDR'; })(formipay_post_currency($post_id)),
];
}
return $form_data;
}
}
?>