first commit

This commit is contained in:
dwindown
2025-08-21 20:39:34 +07:00
commit 58c1497171
576 changed files with 177044 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
<?php
/**
* File ini dipanggil oleh fungsi admin WordPress Anda untuk merender canvas.
*/
// 1. Ambil data awal dari WordPress
$post_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
$saved_config_json = '[]';
if ($post_id > 0) {
$formipay_post_meta = get_post_meta($post_id, 'formipay_settings', true);
if (!empty($formipay_post_meta['fields'])) {
$saved_config_json = json_encode($formipay_post_meta['fields']);
}
}
// Data yang akan kita lewatkan ke Vue
// Menggantikan objek `formipay_admin` dari kode lama
$vue_app_data = [
'savedFields' => json_decode($saved_config_json), // Kirim sebagai objek, bukan string
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('formipay-secure-nonce'),
'formId' => $post_id,
'presets' => [
'countryList' => your_function_to_get_countries() // Panggil fungsi PHP Anda di sini
],
];
?>
<div id="formipay-builder-app" data-app-data='<?php echo esc_attr(json_encode($vue_app_data)); ?>'>
<p>Loading Form Builder...</p>
</div>

View File

@@ -0,0 +1,230 @@
<?php
$saved_config = [];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if(isset($_GET['post']) && isset($_GET['action']) && $_GET['action'] == 'edit'){
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$formipay_post_meta = get_post_meta(intval(wp_unslash($_GET['post'])), 'formipay_settings', true);
$saved_config = $formipay_post_meta['fields'];
}
?>
<div class="d-flex mx-0 w-100">
<div class="p-3" id="sidebar_panel">
<input type="hidden" name="formipay_builder_canvas" value="<?php echo esc_attr( wp_create_nonce('formipay-form-builder-canvas') ) ?>">
<div id="add_field_form"></div>
</div>
<div class="position-relative p-2" id="fields_panel">
<div class="position-absolute fw-bold"><?php echo esc_html__('Preview', 'formipay'); ?></div>
<div id="preview-wrapper" class="h-100 d-flex flex-column gap-2"></div>
</div>
</div>
<!-- Config template -->
<script id="add-field-form-template" type="text/x-handlebars-template">
{{#each fields}}
<div class="field mb-2{{custom_class custom_class}}{{conditional_class conditional}}"{{{display conditional}}}>
<label for="{{id}}" class="form-label mb-0 small text-white {{#ifIn type "checkbox radio"}}d-none{{/ifIn}}">{{label}}</label>
{{#ifIn type "text number"}}
<input type="{{type}}" name="{{id}}" id="{{id}}" class="form-control formipay-builder-field" value="{{default}}" />
{{/ifIn}}
{{#ifEquals type "select"}}
<select name="{{id}}" class="form-select w-100 formipay-builder-field">
<option value="">-- Choose Type</option>
{{#each options}}
<option value="{{@key}}" {{selectedTheFirstOption @index}}>{{this}}</option>
{{/each}}
</select>
{{/ifEquals}}
{{#ifIn type "checkbox radio"}}
<input type="checkbox" name="{{id}}" id="{{id}}" class="form-control formipay-builder-field" value="no" />
<label for="{{id}}" class="form-label mb-0 small text-white">{{label}}</label>
{{/ifIn}}
{{#ifEquals type "textarea"}}
<textarea name="{{id}}" id="{{id}}" class="form-control formipay-builder-field">{{default}}</textarea>
<p class="mb-0 option-description small text-light">{{description}}</p>
{{/ifEquals}}
{{#ifEquals type "repeater"}}
<label class="form-label mb-0 small text-white">{{label}}</label>
<div class="d-flex flex-wrap gap-2 justify-content-start align-items-center">
{{#each fields}}
{{#ifEquals toggle "yes"}}
<div class="label-field">
<input type="checkbox" class="form-control option-field-toggle" data-child-field="{{id}}" id="repeater-child-{{@index}}_{{id}}">
<label for="repeater-child-{{@index}}_{{id}}" class="form-label text-sm mb-0 text-white">Show {{label}}</label>
</div>
{{/ifEquals}}
{{/each}}
</div>
<div class="repeater-child-wrapper">
<div class="repeater-child-input d-flex justify-content-start align-items-start gap-2 my-2">
<span class="grab mt-2">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
<path fill="#fff" d="M9 20q-.825 0-1.412-.587T7 18t.588-1.412T9 16t1.413.588T11 18t-.587 1.413T9 20m6 0q-.825 0-1.412-.587T13 18t.588-1.412T15 16t1.413.588T17 18t-.587 1.413T15 20m-6-6q-.825 0-1.412-.587T7 12t.588-1.412T9 10t1.413.588T11 12t-.587 1.413T9 14m6 0q-.825 0-1.412-.587T13 12t.588-1.412T15 10t1.413.588T17 12t-.587 1.413T15 14M9 8q-.825 0-1.412-.587T7 6t.588-1.412T9 4t1.413.588T11 6t-.587 1.413T9 8m6 0q-.825 0-1.412-.587T13 6t.588-1.412T15 4t1.413.588T17 6t-.587 1.413T15 8" />
</svg>
</span>
<div class="child-fields-wrapper w-100">
<div class="child-field-title text-bg-dark py-2 px-3 d-flex justify-content-between align-items-center">
<div class="the_title">Option</div>
<div class="the_buttons">
<button class="btn btn-sm text-bg-light mb-0 add-option">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="18" viewBox="0 0 24 24">
<path fill="#000" d="M9 18q-.825 0-1.412-.587T7 16V4q0-.825.588-1.412T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.587 1.413T18 18zm0-2h9V4H9zm-4 6q-.825 0-1.412-.587T3 20V6h2v14h11v2zm4-6V4z" />
</svg>
</button>
<button class="btn btn-sm text-bg-light mb-0 delete-option">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
<path fill="#000" d="m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z" />
</svg>
</i></button>
<button class="btn btn-sm text-bg-light mb-0 open-collapse open-option">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
<path fill="#000" d="m12 15l-5-5h10z" />
</svg>
</button>
</div>
</div>
<div class="child-field-wrapper p-2 bg-light text-dark" style="display:none;">
<div class="child-field-row row">
<div class="child-field-image col-6 child-field-col-1 child-field-thumbnail repeater-child-input-thumbnail" style="display: none;">
<div class="child-field-image-wrapper formipay-builder-field">
<?php // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage -- This image is a placeholder to display an image preview and will be managed by JS ?>
<img src="" class="d-none" style="width: 100%;" alt="Image Preview"/>
<i data-field="field-image" class="bi bi-file-earmark-image-fill pointer fs-3 text-secondary add-thumbnail child-field-image"{{{toggleDisplay toggle}}}></i>
<input type="hidden" class="field-image-id d-none">
<input type="hidden" class="field-image-url d-none">
<button data-field="field-image" class="btn btn-sm add-thumbnail bg-secondary text-white">Choose Image</button>
</div>
</div>
<div class="col child-field-col-2">
<table class="table child-field-input-table">
<tbody>
{{#each fields}}
{{#ifNotIn type "image toggle"}}
<tr class="child-field-{{id}} repeater-child-input-{{id}}"{{{toggleDisplay toggle}}}>
<th>{{label}}</th>
<td>
<input type="{{type}}" class="form-control formipay-builder-field formipay-builder-option-field" value="{{default}}" placeholder="{{label}}" />
</td>
</tr>
{{/ifNotIn}}
{{#ifEquals type "toggle"}}
<tr class="child-field-{{id}}"{{{toggleDisplay toggle}}}>
<th>{{label}}</th>
<td>
<div class="form-check form-switch align-items-baseline mt-2 mb-0 formipay-builder-field formipay-builder-option-field">
<input class="form-check-input check-qty repeater-child-input-qty" type="checkbox" role="switch">
<label class="form-check-label text-white text-nowrap"><span class="small">✕</span> Quantity</label>
</div>
</td>
</tr>
{{/ifEquals}}
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{{/ifEquals}}
</div>
{{/each}}
<button type="button" class="btn btn-light add-field w-100">Add Field</button>
</script>
<!-- Preview template -->
<script id="preview-template" type="text/x-handlebars-template">
{{#each fields}}
{{!-- Hidden field --}}
<div class="preview-field d-flex gap-2 align-items-center" data-field="{{field_id}}" data-field-type="{{field_type}}" {{#ifEquals field_type "hidden"}}style="opacity: .5;"{{/ifEquals}}>
<span class="grab pb-4">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
<path fill="#000" d="M9 20q-.825 0-1.412-.587T7 18t.588-1.412T9 16t1.413.588T11 18t-.587 1.413T9 20m6 0q-.825 0-1.412-.587T13 18t.588-1.412T15 16t1.413.588T17 18t-.587 1.413T15 20m-6-6q-.825 0-1.412-.587T7 12t.588-1.412T9 10t1.413.588T11 12t-.587 1.413T9 14m6 0q-.825 0-1.412-.587T13 12t.588-1.412T15 10t1.413.588T17 12t-.587 1.413T15 14M9 8q-.825 0-1.412-.587T7 6t.588-1.412T9 4t1.413.588T11 6t-.587 1.413T9 8m6 0q-.825 0-1.412-.587T13 6t.588-1.412T15 4t1.413.588T17 6t-.587 1.413T15 8" />
</svg>
</span>
<div class="formipay-field w-100">
<div class="d-flex justify-content-between field-controls">
<label for="{{field_id}}" class="{{labelClass field_type}}">{{label}}{{#ifEquals is_required "yes"}} <span style="color: red;">(*)</span>{{/ifEquals}}</label>
<div class="field-icons d-flex gap-2 align-items-center the_buttons">
<button class="btn btn-sm edit-preview-field">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
<path fill="#000" d="M5 19h1.425L16.2 9.225L14.775 7.8L5 17.575zm-2 2v-4.25L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.438.65T21 6.4q0 .4-.137.763t-.438.662L7.25 21zM19 6.4L17.6 5zm-3.525 2.125l-.7-.725L16.2 9.225z" />
</svg>
</button>
<button class="btn btn-sm delete-preview-field">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
<path fill="#000" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z" />
</svg>
</button>
</div>
</div>
<input type="hidden" name="{{@key}}" class="formipay-field-setup" value='{{json this}}' />
{{!-- Input fields --}}
{{#ifNotEquals field_type "textarea"}}
{{#ifIn field_type "divider page_break select checkbox radio country_list"}}
{{#ifEquals field_type "divider"}}
<span class="divider-line"></span>
{{/ifEquals}}
{{#ifEquals field_type "page_break"}}
<span class="divider-line"></span>
{{/ifEquals}}
{{#ifEquals field_type "select"}}
<select id="{{field_id}}" class="formipay-input formipay-form-field w-100">
{{#if placeholder}}
<option>{{placeholder}}</option>
{{/if}}
{{#each field_options}}
<option value="{{this.value}}" data-calc-value="{{this.amount}}">{{this.label}}</option>
{{/each}}
</select>
{{/ifEquals}}
{{#ifEquals field_type "country_list"}}
<select id="{{field_id}}" class="formipay-input formipay-form-field w-100">
<option>-- {{placeholder}}</option>
{{{countryListOptions}}}
</select>
{{/ifEquals}}
{{#ifIn field_type "checkbox radio"}}
<div class="formipay-checkbox-wrapper" style="grid-template-columns: repeat({{layoutColumn layout}}, 1fr);">
{{#each field_options}}
<div class="formipay-checkbox-group">
{{#if image_url}}
<?php // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage -- This image is a placeholder to display an image preview and will be managed by JS ?>
<img src="{{this.image_url}}" alt="{{this.label}}" style="width: 100%; height: 150px; object-fit: contain;" />
{{/if}}
<input type="{{../field_type}}" id="{{../config_id}}-{{@index}}" name="{{name ../field_type ../field_id @index}}" class="formipay-input formipay-form-field" value="{{this.value}}" data-calc-value="{{this.amount}}"/>
<label for="{{../config_id}}-{{@index}}">{{this.label}}</label>
</div>
{{/each}}
</div>
{{/ifIn}}
{{else}}
<input type="{{field_type}}" id="{{field_id}}" class="formipay-input formipay-form-field w-100" placeholder="{{placeholder}}" value="{{default_value}}" {{#ifEquals field_type "number"}}data-calc-value="{{calc_value}}"{{/ifEquals}} />
{{/ifIn}}
{{else}}
{{!-- Textarea field --}}
<textarea id="{{field_id}}" rows="4" class="formipay-input formipay-form-field w-100" placeholder="{{placeholder}}">{{default_value}}</textarea>
{{/ifNotEquals}}
<p class="formipay-inline-desc">{{description}}</p>
</div>
{{#ifEquals field_type "page_break"}}
<span class="scissors">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
<path fill="#000" d="m12 14l-2.35 2.35q.2.375.275.8T10 18q0 1.65-1.175 2.825T6 22t-2.825-1.175T2 18t1.175-2.825T6 14q.425 0 .85.075t.8.275L10 12L7.65 9.65q-.375.2-.8.275T6 10q-1.65 0-2.825-1.175T2 6t1.175-2.825T6 2t2.825 1.175T10 6q0 .425-.075.85t-.275.8L20.6 18.6q.675.675.3 1.538T19.575 21q-.275 0-.537-.112t-.463-.313zm3-3l-2-2l5.575-5.575q.2-.2.463-.312T19.574 3q.95 0 1.313.875t-.313 1.55zM6 8q.825 0 1.413-.587T8 6t-.587-1.412T6 4t-1.412.588T4 6t.588 1.413T6 8m6 4.5q.2 0 .35-.15t.15-.35t-.15-.35t-.35-.15t-.35.15t-.15.35t.15.35t.35.15M6 20q.825 0 1.413-.587T8 18t-.587-1.412T6 16t-1.412.588T4 18t.588 1.413T6 20" />
</svg>
</span>
{{/ifEquals}}
</div>
{{/each}}
</script>

View File

@@ -0,0 +1,48 @@
<div class="global-paypal-instruction" style="display:none;">
<h5>Step 1: Create a PayPal Business Account</h5>
<ul class="paypal-instruction-point">
<li><b>Go to PayPal Website:</b> Open your web browser and go to PayPal\'s website.</li>
<li><b>Sign Up:</b> Click on <u>Sign Up</u> in the top right corner.</li>
<li><b>Select Business Account:</b> Choose the "Business Account" option and click <b>Continue</b>.</li>
<li><b>Fill in Your Information:</b> Enter your email address and create a password. Follow the steps to fill in your business information (name, address, etc.).</li>
<li><b>Complete Verification:</b> PayPal may ask you to verify your identity by confirming your email address and phone number. Follow the prompts in your email and on the website to complete this step.</li>
</ul>
<h5>Step 2: Access PayPal Developer Portal.</h5>
<ul class="paypal-instruction-point">
<li><b>Go to PayPal Developer Website:</b> Open a new browser tab and navigate to the <a href="https://developer.paypal.com/" target="_blank"><u>PayPal Developer site</u></a>.</li>
<li><b>Log In:</b> Click “Log In to Dashboard” in the top-right corner and use your PayPal business account credentials to log in.</li>
</ul>
<h5>Step 3: Create PayPal REST API App</h5>
<ul class="paypal-instruction-point">
<li><b>Navigate to Dashboard:</b> Once logged in, you will see the Developer Dashboard.</li>
<li><b>Click on <u>My Apps & Credentials</u></b>: Look for this option in the left sidebar.</li>
<li><span><b>Create an App:</b></span>
<ul>
<li>Find the “REST API apps” section.</li>
<li>Click the "Create App" button.</li>
</ul>
</li>
<li><b>Name Your App:</b> Give your app a name (e.g., “My WordPress Store”) and click “Create App”.</li>
<li><span><b>View Your Credentials:</b></span> Once created, you\'ll see your new app. Here you will find:
<ul>
<li>Client ID</li>
<li>Secret</li>
<li>Sandbox and Live credentials (make sure to toggle between Sandbox and Live as needed)</li>
</ul>
</li>
</ul>
<h5>Step 4: Gather Required Merchant Data</h5>
<p>Now that you have created your PayPal app, you can gather the required merchant data.</p>
<ul class="paypal-instruction-point">
<li><b>PayPal Client ID:</b> Copy the Client ID from your app settings.</li>
<li><b>PayPal Secret:</b> Copy the Secret from your app settings.</li>
<li><b>PayPal Environment:</b> Decide whether you want to work in Sandbox (for testing) or Live (for actual transactions). Make a note of which environment you are using.</li>
<li><b>Merchant Email:</b> Log in to your PayPal account and note the email address linked to your account. This is typically the email you used when signing up.</li>
</ul>
<h5>Final Notes</h5>
<ul>
<li>Keep your PayPal Client ID and Secret secure; do not share them publicly.</li>
<li>Always test your integration in the Sandbox environment before going Live.</li>
</ul>
</div>
<div id="connect-status"></div>

View File

@@ -0,0 +1,114 @@
<div id="product-variables-table" class="wpcfto_generic_field wpcfto_generic_field_html">
<div class="wpcfto-field-content">
<table class="wpcfto-table formipay-variations-table">
<thead>
<tr>
<th data-cell="toggle">{{ product_details.variation_table.th_toggle }}</th>
<th data-cell="name">{{ product_details.variation_table.th_name }}</th>
<th data-cell="stock">{{ product_details.variation_table.th_stock }}</th>
<th data-cell="weight" v-if="isPhysical">{{ product_details.variation_table.th_weight }}</th>
<template v-if="pricingMethod !== 'manual'">
<th data-cell="price">{{ product_details.variation_table.th_price }}</th>
<th data-cell="sale">{{ product_details.variation_table.th_sale }}</th>
</template>
<template v-else>
<th data-cell="prices">{{ product_details.variation_table.th_prices_overall }}</th>
</template>
<th class="toggle-column"></th>
</tr>
</thead>
<tbody v-if="tableRows.length > 0">
<template v-for="(row, idx) in tableRows">
<tr :key="row.key" :class="['wpcfto-repeater-single', { 'variation-inactive': !row.active }]">
<td data-cell="toggle" style="text-align:center;">
<label class="switch">
<input type="checkbox" v-model="row.active" />
<span class="slider"></span>
</label>
</td>
<td data-cell="name">
<input type="text" v-model="row.name" class="wpcfto-admin-input" readonly :disabled="!row.active" />
</td>
<td data-cell="stock">
<input type="number" v-model.number="row.stock" class="wpcfto-admin-input" min="0" :placeholder="'∞'" style="width:100%;text-align:right;" :disabled="!row.active" />
</td>
<td v-if="isPhysical" data-cell="weight">
<input type="number" v-model.number="row.weight" step="0.01" style="width:100%; text-align:right;" :disabled="!row.active" />
</td>
<template v-if="pricingMethod !== 'manual'">
<td data-cell="price">
<price-input :value="row.price" @input="row.price = $event" :currency-symbol="currencySymbol" :currency-decimal-digits="currencyDecimalDigits" :disabled="!row.active" />
</td>
<td data-cell="sale">
<price-input :value="row.sale" @input="row.sale = $event" :currency-symbol="currencySymbol" :currency-decimal-digits="currencyDecimalDigits" :disabled="!row.active" />
</td>
</template>
<template v-else>
<td data-cell="prices" class="manual-price-hint">
<span @click="row.expanded = !row.expanded">{{ product_details.variation_table.manual_price_hint }}</span>
</td>
</template>
<td class="toggle-column" style="text-align:center;">
<button v-if="pricingMethod === 'manual' && row.prices.length > 0" @click="row.expanded = !row.expanded" class="button-link child-row-toggle" :class="row.expanded ? 'active' : ''" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
<circle cx="12" cy="12" r="10" />
<path d="M15 12.915S12.79 15.5 12 15.5s-3-2.585-3-2.585M15 8.5s-2.21 2.585-3 2.585S9 8.5 9 8.5" />
</g>
</svg>
</button>
</td>
</tr>
<tr v-if="pricingMethod === 'manual' && row.expanded" :key="row.key + '-details'" class="variation-details-row">
<td :colspan="isPhysical ? 7 : 6">
<div class="variation-details-content">
<table class="wpcfto-table inner-table" v-if="row.prices.length > 0">
<thead>
<tr>
<th>{{ product_details.variation_table.child_th_currency }}</th>
<th>{{ product_details.variation_table.child_th_regular }}</th>
<th>{{ product_details.variation_table.child_th_sale }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(priceEntry, p_idx) in row.prices" :key="p_idx">
<td>{{ priceEntry.currency.split(':::')[1] }} ({{ priceEntry.currency.split(':::')[2] || priceEntry.currency.split(':::')[0] }})</td>
<td>
<price-input :value="priceEntry.regular_price" @input="priceEntry.regular_price = $event" :currency-symbol="priceEntry.currency.split(':::')[2] || priceEntry.currency.split(':::')[0]" :currency-decimal-digits="parseInt(priceEntry.currency_decimal_digits)" :disabled="!row.active" />
</td>
<td>
<price-input :value="priceEntry.sale_price" @input="priceEntry.sale_price = $event" :currency-symbol="priceEntry.currency.split(':::')[2] || priceEntry.currency.split(':::')[0]" :currency-decimal-digits="parseInt(priceEntry.currency_decimal_digits)" :disabled="!row.active" />
</td>
</tr>
</tbody>
</table>
<p v-else style="padding: 10px; text-align:center;">{{ product_details.variation_table.no_currencies }}</p>
</div>
</td>
</tr>
</template>
</tbody>
<tbody v-else>
<tr>
<td :colspan="isPhysical ? 7 : 6" style="text-align:center;color:#8c99a5;">
{{ product_details.variation_table.no_variations }}
</td>
</tr>
</tbody>
</table>
<input type="hidden" name="product_variables" v-model="jsonValue" />
</div>
</div>