feat(Rule): add new rules
This commit is contained in:
@@ -231,6 +231,11 @@ add_action('admin_enqueue_scripts', function($hook) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rule builder scripts
|
||||||
|
if (strpos($hook, 'sodino-add-rule') !== false || strpos($hook, 'sodino_page_sodino-add-rule') !== false) {
|
||||||
|
wp_enqueue_script('sodino-rule-admin', plugin_dir_url(__FILE__) . 'js/rule-admin.js', [], SODINO_VERSION, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Banner specific scripts
|
// Banner specific scripts
|
||||||
if (strpos($hook, 'sodino-add-banner') !== false || strpos($hook, 'sodino_page_sodino-add-banner') !== false) {
|
if (strpos($hook, 'sodino-add-banner') !== false || strpos($hook, 'sodino_page_sodino-add-banner') !== false) {
|
||||||
wp_enqueue_media();
|
wp_enqueue_media();
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ class Sodino_Rules_List_Table extends WP_List_Table {
|
|||||||
return [
|
return [
|
||||||
'cb' => '<input type="checkbox" />',
|
'cb' => '<input type="checkbox" />',
|
||||||
'name' => __('عنوان قانون', 'sodino'),
|
'name' => __('عنوان قانون', 'sodino'),
|
||||||
'condition_type' => __('شرط', 'sodino'),
|
'conditions' => __('شرطها', 'sodino'),
|
||||||
'action_value' => __('عملیات', 'sodino'),
|
'actions_summary'=> __('عملیاتها', 'sodino'),
|
||||||
|
'usage' => __('استفاده', 'sodino'),
|
||||||
'enabled' => __('وضعیت', 'sodino'),
|
'enabled' => __('وضعیت', 'sodino'),
|
||||||
'actions' => __('عملیات', 'sodino'),
|
'actions' => __('عملیات', 'sodino'),
|
||||||
];
|
];
|
||||||
@@ -68,31 +69,64 @@ class Sodino_Rules_List_Table extends WP_List_Table {
|
|||||||
return $title;
|
return $title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function column_condition_type($item) {
|
private function conditionLabels() {
|
||||||
$labels = [
|
return [
|
||||||
'user_type' => __('نوع کاربر', 'sodino'),
|
'user_type' => __('نوع کاربر', 'sodino'),
|
||||||
'product_category' => __('دستهبندی محصول', 'sodino'),
|
'product_category' => __('دستهبندی محصول', 'sodino'),
|
||||||
|
'exclude_product_category' => __('بهجز دستهبندی', 'sodino'),
|
||||||
|
'product_tag' => __('برچسب محصول', 'sodino'),
|
||||||
'product_ids' => __('محصولات خاص', 'sodino'),
|
'product_ids' => __('محصولات خاص', 'sodino'),
|
||||||
|
'exclude_product_ids' => __('بهجز محصولات', 'sodino'),
|
||||||
'cart_total_min' => __('حداقل مبلغ سبد', 'sodino'),
|
'cart_total_min' => __('حداقل مبلغ سبد', 'sodino'),
|
||||||
'cart_total_max' => __('حداکثر مبلغ سبد', 'sodino'),
|
'cart_total_max' => __('حداکثر مبلغ سبد', 'sodino'),
|
||||||
'cart_item_count_min' => __('حداقل تعداد سبد', 'sodino'),
|
'cart_item_count_min' => __('حداقل تعداد سبد', 'sodino'),
|
||||||
'cart_item_count_max' => __('حداکثر تعداد سبد', 'sodino'),
|
'cart_item_count_max' => __('حداکثر تعداد سبد', 'sodino'),
|
||||||
|
'cart_contains_product' => __('سبد شامل محصول', 'sodino'),
|
||||||
|
'cart_contains_category' => __('سبد شامل دستهبندی', 'sodino'),
|
||||||
|
'customer_order_count_min' => __('حداقل سفارش مشتری', 'sodino'),
|
||||||
|
'customer_order_count_max' => __('حداکثر سفارش مشتری', 'sodino'),
|
||||||
|
'day_of_week' => __('روز هفته', 'sodino'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$type = $labels[$item->condition_type] ?? $item->condition_type;
|
|
||||||
return esc_html(sprintf('%s: %s', $type, $item->condition_value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function column_action_value($item) {
|
public function column_conditions($item) {
|
||||||
|
$labels = [
|
||||||
|
];
|
||||||
|
$labels = $this->conditionLabels();
|
||||||
|
$parts = [];
|
||||||
|
foreach ((array) $item->conditions as $condition) {
|
||||||
|
$type = $condition['type'] ?? '';
|
||||||
|
$value = $condition['value'] ?? '';
|
||||||
|
$parts[] = esc_html(sprintf('%s: %s', $labels[$type] ?? $type, is_array($value) ? implode(',', $value) : $value));
|
||||||
|
}
|
||||||
|
return $parts ? implode('<br>', $parts) : esc_html__('بدون شرط', 'sodino');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_actions_summary($item) {
|
||||||
$labels = [
|
$labels = [
|
||||||
'discount_percent' => __('درصد تخفیف', 'sodino'),
|
'discount_percent' => __('درصد تخفیف', 'sodino'),
|
||||||
'discount_fixed' => __('تخفیف ثابت', 'sodino'),
|
'discount_fixed' => __('تخفیف ثابت', 'sodino'),
|
||||||
'set_price' => __('قیمت ثابت', 'sodino'),
|
'set_price' => __('قیمت ثابت', 'sodino'),
|
||||||
|
'increase_percent' => __('افزایش درصدی', 'sodino'),
|
||||||
|
'increase_fixed' => __('افزایش ثابت', 'sodino'),
|
||||||
'free_shipping' => __('ارسال رایگان', 'sodino'),
|
'free_shipping' => __('ارسال رایگان', 'sodino'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$type = $labels[$item->action_type] ?? $item->action_type;
|
$parts = [];
|
||||||
return esc_html(sprintf('%s: %s', $type, $item->action_value));
|
foreach ((array) $item->actions as $action) {
|
||||||
|
$type = $action['type'] ?? '';
|
||||||
|
$value = $action['value'] ?? 0;
|
||||||
|
$parts[] = esc_html(sprintf('%s: %s', $labels[$type] ?? $type, $type === 'free_shipping' ? '-' : $value));
|
||||||
|
}
|
||||||
|
return $parts ? implode('<br>', $parts) : esc_html__('بدون عملیات', 'sodino');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_usage($item) {
|
||||||
|
$limit = (int) $item->usage_limit;
|
||||||
|
if ($limit <= 0) {
|
||||||
|
return esc_html(sprintf('%s / %s', number_format_i18n($item->usage_count), __('نامحدود', 'sodino')));
|
||||||
|
}
|
||||||
|
return esc_html(sprintf('%s / %s', number_format_i18n($item->usage_count), number_format_i18n($limit)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function column_enabled($item) {
|
public function column_enabled($item) {
|
||||||
@@ -102,8 +136,9 @@ class Sodino_Rules_List_Table extends WP_List_Table {
|
|||||||
public function column_default($item, $column_name) {
|
public function column_default($item, $column_name) {
|
||||||
switch ($column_name) {
|
switch ($column_name) {
|
||||||
case 'name':
|
case 'name':
|
||||||
case 'condition_type':
|
case 'conditions':
|
||||||
case 'action_value':
|
case 'actions_summary':
|
||||||
|
case 'usage':
|
||||||
case 'enabled':
|
case 'enabled':
|
||||||
case 'actions':
|
case 'actions':
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
37
admin/js/rule-admin.js
Normal file
37
admin/js/rule-admin.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
(function () {
|
||||||
|
function nextIndex(container) {
|
||||||
|
return container.querySelectorAll('[data-row]').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRow(target) {
|
||||||
|
const container = document.getElementById(target === 'actions' ? 'sodino-actions' : 'sodino-conditions');
|
||||||
|
const template = document.getElementById(target === 'actions' ? 'sodino-action-template' : 'sodino-condition-template');
|
||||||
|
if (!container || !template) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = nextIndex(container);
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.innerHTML = template.innerHTML.replaceAll('__INDEX__', String(index)).trim();
|
||||||
|
container.appendChild(wrapper.firstElementChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
const addButton = event.target.closest('.sodino-add-row');
|
||||||
|
if (addButton) {
|
||||||
|
event.preventDefault();
|
||||||
|
addRow(addButton.dataset.target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeButton = event.target.closest('.sodino-remove-row');
|
||||||
|
if (removeButton) {
|
||||||
|
event.preventDefault();
|
||||||
|
const row = removeButton.closest('[data-row]');
|
||||||
|
const container = row ? row.parentElement : null;
|
||||||
|
if (container && container.querySelectorAll('[data-row]').length > 1) {
|
||||||
|
row.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
33
admin/views/partials/rule-action-row.php
Normal file
33
admin/views/partials/rule-action-row.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action_type = $action['type'] ?? 'discount_percent';
|
||||||
|
$action_value = $action['value'] ?? 0;
|
||||||
|
?>
|
||||||
|
<div class="sodino-rule-row rounded-lg border border-gray-200 bg-gray-50 p-4" data-row>
|
||||||
|
<div class="grid gap-4 lg:grid-cols-[1.5fr_1fr_auto]">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-500 mb-2"><?php _e('نوع عملیات', 'sodino'); ?></label>
|
||||||
|
<select name="actions[<?php echo esc_attr($index); ?>][type]" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-700">
|
||||||
|
<option value="discount_percent" <?php selected($action_type, 'discount_percent'); ?>><?php _e('تخفیف درصدی', 'sodino'); ?></option>
|
||||||
|
<option value="discount_fixed" <?php selected($action_type, 'discount_fixed'); ?>><?php _e('تخفیف ثابت', 'sodino'); ?></option>
|
||||||
|
<option value="set_price" <?php selected($action_type, 'set_price'); ?>><?php _e('تنظیم قیمت ثابت', 'sodino'); ?></option>
|
||||||
|
<option value="increase_percent" <?php selected($action_type, 'increase_percent'); ?>><?php _e('افزایش درصدی قیمت', 'sodino'); ?></option>
|
||||||
|
<option value="increase_fixed" <?php selected($action_type, 'increase_fixed'); ?>><?php _e('افزایش ثابت قیمت', 'sodino'); ?></option>
|
||||||
|
<option value="free_shipping" <?php selected($action_type, 'free_shipping'); ?>><?php _e('ارسال رایگان', 'sodino'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-500 mb-2"><?php _e('مقدار', 'sodino'); ?></label>
|
||||||
|
<input type="number" name="actions[<?php echo esc_attr($index); ?>][value]" value="<?php echo esc_attr($action_value); ?>" min="0" step="0.01" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-700">
|
||||||
|
<p class="mt-2 text-xs text-gray-500"><?php _e('برای ارسال رایگان میتواند 0 بماند.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-end">
|
||||||
|
<button type="button" class="sodino-remove-row rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm font-semibold text-red-700 hover:bg-red-100"><?php _e('حذف', 'sodino'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
53
admin/views/partials/rule-condition-row.php
Normal file
53
admin/views/partials/rule-condition-row.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$condition_type = $condition['type'] ?? 'user_type';
|
||||||
|
$condition_operator = $condition['operator'] ?? 'is';
|
||||||
|
$condition_value = $condition['value'] ?? '';
|
||||||
|
?>
|
||||||
|
<div class="sodino-rule-row rounded-lg border border-gray-200 bg-gray-50 p-4" data-row>
|
||||||
|
<div class="grid gap-4 lg:grid-cols-[1.2fr_1fr_1.5fr_auto]">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-500 mb-2"><?php _e('نوع شرط', 'sodino'); ?></label>
|
||||||
|
<select name="conditions[<?php echo esc_attr($index); ?>][type]" class="sodino-condition-type w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-700">
|
||||||
|
<option value="user_type" <?php selected($condition_type, 'user_type'); ?>><?php _e('نوع کاربر', 'sodino'); ?></option>
|
||||||
|
<option value="product_ids" <?php selected($condition_type, 'product_ids'); ?>><?php _e('محصولات خاص', 'sodino'); ?></option>
|
||||||
|
<option value="exclude_product_ids" <?php selected($condition_type, 'exclude_product_ids'); ?>><?php _e('بهجز محصولات', 'sodino'); ?></option>
|
||||||
|
<option value="product_category" <?php selected($condition_type, 'product_category'); ?>><?php _e('دستهبندی محصول', 'sodino'); ?></option>
|
||||||
|
<option value="exclude_product_category" <?php selected($condition_type, 'exclude_product_category'); ?>><?php _e('بهجز دستهبندی', 'sodino'); ?></option>
|
||||||
|
<option value="product_tag" <?php selected($condition_type, 'product_tag'); ?>><?php _e('برچسب محصول', 'sodino'); ?></option>
|
||||||
|
<option value="cart_total_min" <?php selected($condition_type, 'cart_total_min'); ?>><?php _e('حداقل مبلغ سبد', 'sodino'); ?></option>
|
||||||
|
<option value="cart_total_max" <?php selected($condition_type, 'cart_total_max'); ?>><?php _e('حداکثر مبلغ سبد', 'sodino'); ?></option>
|
||||||
|
<option value="cart_item_count_min" <?php selected($condition_type, 'cart_item_count_min'); ?>><?php _e('حداقل تعداد آیتم سبد', 'sodino'); ?></option>
|
||||||
|
<option value="cart_item_count_max" <?php selected($condition_type, 'cart_item_count_max'); ?>><?php _e('حداکثر تعداد آیتم سبد', 'sodino'); ?></option>
|
||||||
|
<option value="cart_contains_product" <?php selected($condition_type, 'cart_contains_product'); ?>><?php _e('سبد شامل محصول', 'sodino'); ?></option>
|
||||||
|
<option value="cart_contains_category" <?php selected($condition_type, 'cart_contains_category'); ?>><?php _e('سبد شامل دستهبندی', 'sodino'); ?></option>
|
||||||
|
<option value="customer_order_count_min" <?php selected($condition_type, 'customer_order_count_min'); ?>><?php _e('حداقل سفارش مشتری', 'sodino'); ?></option>
|
||||||
|
<option value="customer_order_count_max" <?php selected($condition_type, 'customer_order_count_max'); ?>><?php _e('حداکثر سفارش مشتری', 'sodino'); ?></option>
|
||||||
|
<option value="day_of_week" <?php selected($condition_type, 'day_of_week'); ?>><?php _e('روز هفته', 'sodino'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-500 mb-2"><?php _e('عملگر', 'sodino'); ?></label>
|
||||||
|
<select name="conditions[<?php echo esc_attr($index); ?>][operator]" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-700">
|
||||||
|
<option value="is" <?php selected($condition_operator, 'is'); ?>><?php _e('برابر است با', 'sodino'); ?></option>
|
||||||
|
<option value="is_not" <?php selected($condition_operator, 'is_not'); ?>><?php _e('برابر نیست با', 'sodino'); ?></option>
|
||||||
|
<option value="in" <?php selected($condition_operator, 'in'); ?>><?php _e('داخل لیست است', 'sodino'); ?></option>
|
||||||
|
<option value="not_in" <?php selected($condition_operator, 'not_in'); ?>><?php _e('داخل لیست نیست', 'sodino'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-500 mb-2"><?php _e('مقدار', 'sodino'); ?></label>
|
||||||
|
<input type="text" name="conditions[<?php echo esc_attr($index); ?>][value]" value="<?php echo esc_attr(is_array($condition_value) ? implode(',', $condition_value) : $condition_value); ?>" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-700" placeholder="<?php esc_attr_e('مثال: new یا 12,18,24', 'sodino'); ?>">
|
||||||
|
<p class="mt-2 text-xs text-gray-500"><?php _e('برای چند مقدار، شناسهها را با کاما جدا کنید. نوع کاربر: guest, new, returning, logged_in', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-end">
|
||||||
|
<button type="button" class="sodino-remove-row rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm font-semibold text-red-700 hover:bg-red-100"><?php _e('حذف', 'sodino'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -5,20 +5,28 @@ if (!defined('ABSPATH')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-add-rule');
|
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-add-rule');
|
||||||
$form_condition_type = function_exists('sodino_old_input') ? sodino_old_input('condition_type', $rule->condition_type) : $rule->condition_type;
|
$conditions = function_exists('sodino_old_input') ? sodino_old_input('conditions', $rule->conditions) : $rule->conditions;
|
||||||
$form_action_type = function_exists('sodino_old_input') ? sodino_old_input('action_type', $rule->action_type) : $rule->action_type;
|
$actions = function_exists('sodino_old_input') ? sodino_old_input('actions', $rule->actions) : $rule->actions;
|
||||||
|
$conditions = is_array($conditions) && $conditions ? array_values($conditions) : [['type' => 'user_type', 'operator' => 'is', 'value' => 'new']];
|
||||||
|
$actions = is_array($actions) && $actions ? array_values($actions) : [['type' => 'discount_percent', 'value' => 10]];
|
||||||
|
$product_categories = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false]);
|
||||||
|
$product_tags = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => false]);
|
||||||
|
$weekdays = [
|
||||||
|
'1' => __('دوشنبه', 'sodino'),
|
||||||
|
'2' => __('سهشنبه', 'sodino'),
|
||||||
|
'3' => __('چهارشنبه', 'sodino'),
|
||||||
|
'4' => __('پنجشنبه', 'sodino'),
|
||||||
|
'5' => __('جمعه', 'sodino'),
|
||||||
|
'6' => __('شنبه', 'sodino'),
|
||||||
|
'7' => __('یکشنبه', 'sodino'),
|
||||||
|
];
|
||||||
?>
|
?>
|
||||||
<div id="sodino-app" class="min-h-screen bg-gray-50" dir="rtl">
|
<div id="sodino-app" class="min-h-screen bg-gray-50" dir="rtl">
|
||||||
<!-- Header -->
|
|
||||||
<div class="bg-white border-b border-gray-200">
|
<div class="bg-white border-b border-gray-200">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="py-6">
|
<div class="py-6">
|
||||||
<div class="flex items-center justify-between">
|
<h1 class="text-3xl font-bold text-gray-900"><?php _e('سودینو', 'sodino'); ?></h1>
|
||||||
<div>
|
<p class="mt-1 text-sm text-gray-500"><?php _e('ساخت قوانین قیمتگذاری پیشرفته برای ووکامرس', 'sodino'); ?></p>
|
||||||
<h1 class="text-3xl font-bold text-gray-900"><?php _e('سودینو', 'sodino'); ?></h1>
|
|
||||||
<p class="mt-1 text-sm text-gray-500"><?php _e('بهینهسازی هوشمند فروش', 'sodino'); ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,151 +34,118 @@ $form_action_type = function_exists('sodino_old_input') ? sodino_old_input('acti
|
|||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<?php if (function_exists('sodino_render_admin_notice')) { sodino_render_admin_notice(); } ?>
|
<?php if (function_exists('sodino_render_admin_notice')) { sodino_render_admin_notice(); } ?>
|
||||||
<div class="flex gap-8">
|
<div class="flex gap-8">
|
||||||
<!-- Sidebar -->
|
<?php include SODINO_PLUGIN_DIR . 'admin/components/sidebar.php'; ?>
|
||||||
<aside class="w-64 flex-shrink-0">
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
||||||
<h2 class="text-lg font-semibold text-gray-900 mb-4"><?php _e('منوی سودینو', 'sodino'); ?></h2>
|
|
||||||
<nav class="space-y-2">
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-dashboard'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-dashboard' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('داشبورد', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-rules'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-rules' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('قوانین', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-add-rule'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-add-rule' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('افزودن قانون', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-upsells'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-upsells' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('آپسل (پیشنهاد فروش)', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-add-upsell'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-add-upsell' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('افزودن آپسل', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-banners'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-banners' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('بنرهای هوشمند', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-add-banner'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-add-banner' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('افزودن بنر', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-tools'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-tools' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-settings'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-settings' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
|
||||||
<?php _e('تنظیمات', 'sodino'); ?>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<main class="flex-1 min-w-0">
|
<main class="flex-1 min-w-0">
|
||||||
<!-- Page Header -->
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl font-semibold text-gray-900"><?php echo $rule->id ? __('ویرایش قانون', 'sodino') : __('افزودن قانون جدید', 'sodino'); ?></h2>
|
<h2 class="text-2xl font-semibold text-gray-900"><?php echo $rule->id ? __('ویرایش قانون پیشرفته', 'sodino') : __('افزودن قانون پیشرفته', 'sodino'); ?></h2>
|
||||||
<p class="mt-2 text-gray-600"><?php _e('قانون قیمتگذاری پویا ایجاد یا ویرایش کنید.', 'sodino'); ?></p>
|
<p class="mt-2 text-gray-600"><?php _e('چند شرط را با هم ترکیب کنید و چند عملیات قیمتگذاری را به ترتیب اعمال کنید.', 'sodino'); ?></p>
|
||||||
</div>
|
</div>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-rules'); ?>" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200">
|
<a href="<?php echo esc_url(admin_url('admin.php?page=sodino-rules')); ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50">
|
||||||
<svg class="-ml-1 mr-2 h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
<?php _e('بازگشت به قوانین', 'sodino'); ?>
|
<?php _e('بازگشت به قوانین', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form -->
|
<form method="post" class="space-y-6" id="sodino-rule-form">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
<?php wp_nonce_field('sodino_save_rule', 'sodino_rule_nonce'); ?>
|
||||||
<form method="post">
|
|
||||||
<?php wp_nonce_field('sodino_save_rule', 'sodino_rule_nonce'); ?>
|
|
||||||
|
|
||||||
|
<section class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-5"><?php _e('اطلاعات اصلی', 'sodino'); ?></h3>
|
||||||
<div class="grid gap-6 md:grid-cols-2">
|
<div class="grid gap-6 md:grid-cols-2">
|
||||||
<!-- Rule Name -->
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('عنوان قانون', 'sodino'); ?></label>
|
<label for="name" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('عنوان قانون', 'sodino'); ?></label>
|
||||||
<input type="text" name="name" id="name" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('name', $rule->name) : $rule->name); ?>" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" required>
|
<input type="text" name="name" id="name" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('name', $rule->name) : $rule->name); ?>" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" required>
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('نامی کوتاه و قابل فهم برای قانون انتخاب کنید.', 'sodino'); ?></p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Priority -->
|
|
||||||
<div>
|
<div>
|
||||||
<label for="priority" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('اولویت', 'sodino'); ?></label>
|
<label for="priority" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('اولویت', 'sodino'); ?></label>
|
||||||
<input type="number" name="priority" id="priority" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('priority', $rule->priority ?: 10) : ($rule->priority ?: 10)); ?>" min="1" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100">
|
<input type="number" name="priority" id="priority" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('priority', $rule->priority ?: 10) : ($rule->priority ?: 10)); ?>" min="1" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm">
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('عدد بالاتر، اولویت بیشتری دارد.', 'sodino'); ?></p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Usage Limit -->
|
|
||||||
<div>
|
<div>
|
||||||
<label for="usage_limit" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('محدودیت تعداد استفاده', 'sodino'); ?></label>
|
<label for="usage_limit" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('محدودیت استفاده', 'sodino'); ?></label>
|
||||||
<input type="number" name="usage_limit" id="usage_limit" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('usage_limit', $rule->usage_limit ?? 0) : ($rule->usage_limit ?? 0)); ?>" min="0" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100">
|
<input type="number" name="usage_limit" id="usage_limit" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('usage_limit', $rule->usage_limit ?? 0) : ($rule->usage_limit ?? 0)); ?>" min="0" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm">
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('0 به معنی بدون محدودیت است.', 'sodino'); ?></p>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="start_date" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('شروع', 'sodino'); ?></label>
|
||||||
|
<input type="datetime-local" name="start_date" id="start_date" value="<?php echo esc_attr($rule->start_date ? date('Y-m-d\TH:i', strtotime($rule->start_date)) : ''); ?>" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="end_date" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('پایان', 'sodino'); ?></label>
|
||||||
|
<input type="datetime-local" name="end_date" id="end_date" value="<?php echo esc_attr($rule->end_date ? date('Y-m-d\TH:i', strtotime($rule->end_date)) : ''); ?>" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Roles -->
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<label for="user_roles" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('محدودیت نقش کاربری', 'sodino'); ?></label>
|
<label for="user_roles" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('نقشهای مجاز', 'sodino'); ?></label>
|
||||||
<select name="user_roles[]" id="user_roles" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" multiple size="4">
|
<select name="user_roles[]" id="user_roles" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm" multiple size="4">
|
||||||
<?php foreach (wp_roles()->roles as $role_key => $role_data) : ?>
|
<?php foreach (wp_roles()->roles as $role_key => $role_data) : ?>
|
||||||
<option value="<?php echo esc_attr($role_key); ?>" <?php echo in_array($role_key, (array) $rule->user_roles, true) ? 'selected' : ''; ?>><?php echo esc_html($role_data['name']); ?></option>
|
<option value="<?php echo esc_attr($role_key); ?>" <?php selected(in_array($role_key, (array) $rule->user_roles, true)); ?>><?php echo esc_html($role_data['name']); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('نقشهایی که این قانون باید برای آنها اعمال شود.', 'sodino'); ?></p>
|
<p class="mt-2 text-sm text-gray-500"><?php _e('خالی بماند یعنی برای همه نقشها.', 'sodino'); ?></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Condition Type -->
|
|
||||||
<div>
|
|
||||||
<label for="condition_type" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('نوع شرط', 'sodino'); ?></label>
|
|
||||||
<select name="condition_type" id="condition_type" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" required>
|
|
||||||
<option value="user_type" <?php selected($form_condition_type, 'user_type'); ?>><?php _e('نوع کاربر', 'sodino'); ?></option>
|
|
||||||
<option value="product_category" <?php selected($form_condition_type, 'product_category'); ?>><?php _e('دستهبندی محصول', 'sodino'); ?></option>
|
|
||||||
<option value="product_ids" <?php selected($form_condition_type, 'product_ids'); ?>><?php _e('محصولات خاص', 'sodino'); ?></option>
|
|
||||||
</select>
|
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('نوع شرطی که باید قبل از اعمال قانون بررسی شود.', 'sodino'); ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Condition Value -->
|
|
||||||
<div>
|
|
||||||
<label for="condition_value" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('مقدار شرط', 'sodino'); ?></label>
|
|
||||||
<input type="text" name="condition_value" id="condition_value" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('condition_value', $rule->condition_value) : $rule->condition_value); ?>" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" required>
|
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('برای کاربر جدید/بازگشتی: new یا returning. برای دستهبندی: شناسه دستهبندی، برای محصولات: شناسه محصول.', 'sodino'); ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Type -->
|
|
||||||
<div>
|
|
||||||
<label for="action_type" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('نوع عملیات', 'sodino'); ?></label>
|
|
||||||
<select name="action_type" id="action_type" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" required>
|
|
||||||
<option value="discount_percent" <?php selected($form_action_type, 'discount_percent'); ?>><?php _e('درصد تخفیف', 'sodino'); ?></option>
|
|
||||||
<option value="discount_fixed" <?php selected($form_action_type, 'discount_fixed'); ?>><?php _e('تخفیف ثابت', 'sodino'); ?></option>
|
|
||||||
<option value="free_shipping" <?php selected($form_action_type, 'free_shipping'); ?>><?php _e('ارسال رایگان', 'sodino'); ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Value -->
|
|
||||||
<div>
|
|
||||||
<label for="action_value" class="block text-sm font-medium text-gray-700 mb-2"><?php _e('مقدار عملیات', 'sodino'); ?></label>
|
|
||||||
<input type="number" name="action_value" id="action_value" value="<?php echo esc_attr(function_exists('sodino_old_input') ? sodino_old_input('action_value', $rule->action_value) : $rule->action_value); ?>" min="0" step="0.01" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100">
|
|
||||||
<p class="mt-2 text-sm text-gray-500"><?php _e('برای درصد یا مقدار ثابت وارد کنید.', 'sodino'); ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<label class="flex items-center gap-3 text-gray-700">
|
<label class="flex items-center gap-3 text-gray-700">
|
||||||
<input type="checkbox" name="enabled" id="enabled" value="1" <?php checked($rule->enabled, 1); ?> class="h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
<input type="checkbox" name="enabled" value="1" <?php checked($rule->enabled, 1); ?> class="h-5 w-5 rounded border-gray-300 text-blue-600">
|
||||||
<span><?php _e('فعال باشد', 'sodino'); ?></span>
|
<span><?php _e('قانون فعال باشد', 'sodino'); ?></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<section class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
<div class="mt-8 flex justify-end">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<button type="submit" class="inline-flex items-center px-6 py-3 border border-transparent text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200">
|
<div>
|
||||||
<?php echo $rule->id ? __('بهروزرسانی قانون', 'sodino') : __('افزودن قانون', 'sodino'); ?>
|
<h3 class="text-lg font-semibold text-gray-900"><?php _e('شرطها', 'sodino'); ?></h3>
|
||||||
</button>
|
<p class="mt-1 text-sm text-gray-500"><?php _e('همه شرطهای زیر باید برقرار باشند تا قانون اعمال شود.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="sodino-add-row rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50" data-target="conditions"><?php _e('افزودن شرط', 'sodino'); ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
<div id="sodino-conditions" class="space-y-4">
|
||||||
|
<?php foreach ($conditions as $index => $condition) : ?>
|
||||||
|
<?php include SODINO_PLUGIN_DIR . 'admin/views/partials/rule-condition-row.php'; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<div class="flex items-center justify-between mb-5">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900"><?php _e('عملیاتها', 'sodino'); ?></h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500"><?php _e('عملیاتها به ترتیب روی قیمت اعمال میشوند.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="sodino-add-row rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50" data-target="actions"><?php _e('افزودن عملیات', 'sodino'); ?></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sodino-actions" class="space-y-4">
|
||||||
|
<?php foreach ($actions as $index => $action) : ?>
|
||||||
|
<?php include SODINO_PLUGIN_DIR . 'admin/views/partials/rule-action-row.php'; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-6 py-3 text-sm font-semibold text-white hover:bg-blue-700">
|
||||||
|
<?php echo $rule->id ? __('ذخیره تغییرات قانون', 'sodino') : __('ایجاد قانون', 'sodino'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template id="sodino-condition-template">
|
||||||
|
<?php $index = '__INDEX__'; $condition = ['type' => 'user_type', 'operator' => 'is', 'value' => 'new']; include SODINO_PLUGIN_DIR . 'admin/views/partials/rule-condition-row.php'; ?>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="sodino-action-template">
|
||||||
|
<?php $index = '__INDEX__'; $action = ['type' => 'discount_percent', 'value' => 10]; include SODINO_PLUGIN_DIR . 'admin/views/partials/rule-action-row.php'; ?>
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -22,8 +22,25 @@ class AdminController {
|
|||||||
private $allowedBannerDeviceTargets = ['all', 'desktop', 'mobile'];
|
private $allowedBannerDeviceTargets = ['all', 'desktop', 'mobile'];
|
||||||
private $allowedUpsellTriggerTypes = ['product', 'category', 'cart_total'];
|
private $allowedUpsellTriggerTypes = ['product', 'category', 'cart_total'];
|
||||||
private $allowedUpsellDiscountTypes = ['percentage', 'fixed', 'none'];
|
private $allowedUpsellDiscountTypes = ['percentage', 'fixed', 'none'];
|
||||||
private $allowedRuleConditionTypes = ['user_type', 'product_category', 'product_ids', 'cart_total_min', 'cart_total_max', 'cart_item_count_min', 'cart_item_count_max'];
|
private $allowedRuleConditionTypes = [
|
||||||
private $allowedRuleActionTypes = ['discount_percent', 'discount_fixed', 'set_price', 'free_shipping'];
|
'user_type',
|
||||||
|
'product_category',
|
||||||
|
'exclude_product_category',
|
||||||
|
'product_tag',
|
||||||
|
'product_ids',
|
||||||
|
'exclude_product_ids',
|
||||||
|
'cart_total_min',
|
||||||
|
'cart_total_max',
|
||||||
|
'cart_item_count_min',
|
||||||
|
'cart_item_count_max',
|
||||||
|
'cart_contains_product',
|
||||||
|
'cart_contains_category',
|
||||||
|
'customer_order_count_min',
|
||||||
|
'customer_order_count_max',
|
||||||
|
'day_of_week',
|
||||||
|
];
|
||||||
|
private $allowedRuleConditionOperators = ['is', 'is_not', 'in', 'not_in'];
|
||||||
|
private $allowedRuleActionTypes = ['discount_percent', 'discount_fixed', 'set_price', 'increase_percent', 'increase_fixed', 'free_shipping'];
|
||||||
private $allowedStrategies = ['priority', 'highest_discount', 'first_valid'];
|
private $allowedStrategies = ['priority', 'highest_discount', 'first_valid'];
|
||||||
|
|
||||||
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository, BannerRepository $bannerRepository) {
|
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository, BannerRepository $bannerRepository) {
|
||||||
@@ -667,6 +684,72 @@ class AdminController {
|
|||||||
return $deleted === false ? 0 : (int) $deleted;
|
return $deleted === false ? 0 : (int) $deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function sanitizeRuleConditions($rawConditions) {
|
||||||
|
$conditions = [];
|
||||||
|
$rawConditions = is_array($rawConditions) ? wp_unslash($rawConditions) : [];
|
||||||
|
|
||||||
|
foreach ($rawConditions as $condition) {
|
||||||
|
if (!is_array($condition)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = sanitize_key($condition['type'] ?? '');
|
||||||
|
if (!in_array($type, $this->allowedRuleConditionTypes, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$operator = sanitize_key($condition['operator'] ?? 'is');
|
||||||
|
if (!in_array($operator, $this->allowedRuleConditionOperators, true)) {
|
||||||
|
$operator = 'is';
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = sanitize_text_field($condition['value'] ?? '');
|
||||||
|
if ($value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditions[] = [
|
||||||
|
'type' => $type,
|
||||||
|
'operator' => $operator,
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeRuleActions($rawActions) {
|
||||||
|
$actions = [];
|
||||||
|
$rawActions = is_array($rawActions) ? wp_unslash($rawActions) : [];
|
||||||
|
|
||||||
|
foreach ($rawActions as $action) {
|
||||||
|
if (!is_array($action)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = sanitize_key($action['type'] ?? '');
|
||||||
|
if (!in_array($type, $this->allowedRuleActionTypes, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = max(0, floatval($action['value'] ?? 0));
|
||||||
|
if (in_array($type, ['discount_percent', 'increase_percent'], true)) {
|
||||||
|
$value = min(100, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type !== 'free_shipping' && $value <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actions[] = [
|
||||||
|
'type' => $type,
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $actions;
|
||||||
|
}
|
||||||
|
|
||||||
private function getSettingsDefaults() {
|
private function getSettingsDefaults() {
|
||||||
return [
|
return [
|
||||||
'plugin_enabled' => 1,
|
'plugin_enabled' => 1,
|
||||||
@@ -764,41 +847,30 @@ class AdminController {
|
|||||||
$name = sanitize_text_field($_POST['name'] ?? '');
|
$name = sanitize_text_field($_POST['name'] ?? '');
|
||||||
$this->requireValue($name, __('عنوان قانون الزامی است.', 'sodino'), 'sodino-add-rule');
|
$this->requireValue($name, __('عنوان قانون الزامی است.', 'sodino'), 'sodino-add-rule');
|
||||||
|
|
||||||
$conditionType = sanitize_key($_POST['condition_type'] ?? 'user_type');
|
$conditions = $this->sanitizeRuleConditions($_POST['conditions'] ?? []);
|
||||||
if (!in_array($conditionType, $this->allowedRuleConditionTypes, true)) {
|
if (empty($conditions)) {
|
||||||
$conditionType = 'user_type';
|
$this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('حداقل یک شرط معتبر برای قانون لازم است.', 'sodino'), 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
$conditionValue = sanitize_text_field($_POST['condition_value'] ?? '');
|
$actions = $this->sanitizeRuleActions($_POST['actions'] ?? []);
|
||||||
$this->requireValue($conditionValue, __('مقدار شرط قانون الزامی است.', 'sodino'), 'sodino-add-rule');
|
if (empty($actions)) {
|
||||||
|
$this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('حداقل یک عملیات معتبر برای قانون لازم است.', 'sodino'), 'error');
|
||||||
$actionType = sanitize_key($_POST['action_type'] ?? 'discount_percent');
|
|
||||||
if (!in_array($actionType, $this->allowedRuleActionTypes, true)) {
|
|
||||||
$actionType = 'discount_percent';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$actionValue = sanitize_text_field($_POST['action_value'] ?? '0');
|
$startDate = $this->normalizeDatetime($_POST['start_date'] ?? '');
|
||||||
if ($actionType !== 'free_shipping' && floatval($actionValue) <= 0) {
|
$endDate = $this->normalizeDatetime($_POST['end_date'] ?? '');
|
||||||
$this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('مقدار عملیات باید بزرگتر از صفر باشد.', 'sodino'), 'error');
|
if ($startDate && $endDate && strtotime($endDate) < strtotime($startDate)) {
|
||||||
|
$this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('تاریخ پایان قانون نباید قبل از تاریخ شروع باشد.', 'sodino'), 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
$rule->name = $name;
|
$rule->name = $name;
|
||||||
$rule->priority = max(1, intval($_POST['priority'] ?? 10));
|
$rule->priority = max(1, intval($_POST['priority'] ?? 10));
|
||||||
$rule->usage_limit = max(0, intval($_POST['usage_limit'] ?? 0));
|
$rule->usage_limit = max(0, intval($_POST['usage_limit'] ?? 0));
|
||||||
$rule->user_roles = array_map('sanitize_text_field', (array) ($_POST['user_roles'] ?? []));
|
$rule->user_roles = array_map('sanitize_text_field', (array) ($_POST['user_roles'] ?? []));
|
||||||
|
$rule->start_date = $startDate;
|
||||||
$rule->conditions = [
|
$rule->end_date = $endDate;
|
||||||
[
|
$rule->conditions = $conditions;
|
||||||
'type' => $conditionType,
|
$rule->actions = $actions;
|
||||||
'value' => $conditionValue,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
$rule->actions = [
|
|
||||||
[
|
|
||||||
'type' => $actionType,
|
|
||||||
'value' => $actionValue,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
$rule->syncLegacyFields();
|
$rule->syncLegacyFields();
|
||||||
$rule->enabled = isset($_POST['enabled']) ? 1 : 0;
|
$rule->enabled = isset($_POST['enabled']) ? 1 : 0;
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ class PricingService {
|
|||||||
$oldPrice = $price;
|
$oldPrice = $price;
|
||||||
$price = $this->applyRuleActions($rule, $price);
|
$price = $this->applyRuleActions($rule, $price);
|
||||||
|
|
||||||
if ($price < $oldPrice) {
|
$this->trackDiscountOnce($product, $oldPrice, $price, $rule->id);
|
||||||
$this->trackDiscountOnce($product, $oldPrice, $price, $rule->id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$price = $this->enforceLimits($originalPrice, $price);
|
$price = $this->enforceLimits($originalPrice, $price);
|
||||||
@@ -62,20 +60,16 @@ class PricingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getApplicableRules($product) {
|
private function getApplicableRules($product) {
|
||||||
$cache_key = 'applicable_rules_' . ($product ? $product->get_id() : 'all');
|
$rules = $this->ruleRepository->getEnabled();
|
||||||
|
$applicable = [];
|
||||||
return $this->cache->remember($cache_key, function() use ($product) {
|
|
||||||
$rules = $this->ruleRepository->getEnabled();
|
|
||||||
$applicable = [];
|
|
||||||
|
|
||||||
foreach ($rules as $rule) {
|
foreach ($rules as $rule) {
|
||||||
if ($this->ruleMatches($rule, $product)) {
|
if ($this->ruleMatches($rule, $product)) {
|
||||||
$applicable[] = $rule;
|
$applicable[] = $rule;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $applicable;
|
return $applicable;
|
||||||
}, 300, 'pricing');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function chooseRule(array $rules, $price) {
|
private function chooseRule(array $rules, $price) {
|
||||||
@@ -139,7 +133,7 @@ class PricingService {
|
|||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'user_type':
|
case 'user_type':
|
||||||
return $this->getUserType() === $value;
|
return $this->compareAnyValue($this->getUserTypeAliases(), $value, $condition['operator'] ?? 'is');
|
||||||
case 'cart_total_min':
|
case 'cart_total_min':
|
||||||
return $this->getCartTotal() >= floatval($value);
|
return $this->getCartTotal() >= floatval($value);
|
||||||
case 'cart_total_max':
|
case 'cart_total_max':
|
||||||
@@ -150,8 +144,24 @@ class PricingService {
|
|||||||
return $this->getCartItemCount() <= intval($value);
|
return $this->getCartItemCount() <= intval($value);
|
||||||
case 'product_category':
|
case 'product_category':
|
||||||
return $this->productHasCategory($product, (array) $value);
|
return $this->productHasCategory($product, (array) $value);
|
||||||
|
case 'exclude_product_category':
|
||||||
|
return !$this->productHasCategory($product, (array) $value);
|
||||||
|
case 'product_tag':
|
||||||
|
return $this->productHasTerm($product, (array) $value, 'product_tag');
|
||||||
case 'product_ids':
|
case 'product_ids':
|
||||||
return $this->productIsInIds($product, (array) $value);
|
return $this->productIsInIds($product, (array) $value);
|
||||||
|
case 'exclude_product_ids':
|
||||||
|
return !$this->productIsInIds($product, (array) $value);
|
||||||
|
case 'cart_contains_product':
|
||||||
|
return $this->cartContainsProduct((array) $value);
|
||||||
|
case 'cart_contains_category':
|
||||||
|
return $this->cartContainsCategory((array) $value);
|
||||||
|
case 'customer_order_count_min':
|
||||||
|
return $this->getCustomerOrderCount() >= intval($value);
|
||||||
|
case 'customer_order_count_max':
|
||||||
|
return $this->getCustomerOrderCount() <= intval($value);
|
||||||
|
case 'day_of_week':
|
||||||
|
return in_array((string) current_time('N'), array_map('strval', $this->normalizeIdList($value)), true);
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -168,6 +178,23 @@ class PricingService {
|
|||||||
return $order_count > 0 ? 'returning' : 'new';
|
return $order_count > 0 ? 'returning' : 'new';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getUserTypeAliases() {
|
||||||
|
$type = $this->getUserType();
|
||||||
|
$aliases = [$type];
|
||||||
|
if (is_user_logged_in()) {
|
||||||
|
$aliases[] = 'logged_in';
|
||||||
|
}
|
||||||
|
return $aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomerOrderCount() {
|
||||||
|
if (!is_user_logged_in()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) wc_get_customer_order_count(get_current_user_id());
|
||||||
|
}
|
||||||
|
|
||||||
private function userHasAllowedRole($roles) {
|
private function userHasAllowedRole($roles) {
|
||||||
if (!is_user_logged_in()) {
|
if (!is_user_logged_in()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -197,16 +224,20 @@ class PricingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function productHasCategory($product, $categories) {
|
private function productHasCategory($product, $categories) {
|
||||||
if (!$product || empty($categories)) {
|
return $this->productHasTerm($product, $categories, 'product_cat');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function productHasTerm($product, $terms, $taxonomy) {
|
||||||
|
if (!$product || empty($terms)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$product_cats = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'ids']);
|
$product_terms = wp_get_post_terms($product->get_id(), $taxonomy, ['fields' => 'ids']);
|
||||||
if (is_wp_error($product_cats)) {
|
if (is_wp_error($product_terms)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (bool) array_intersect($product_cats, $this->normalizeIdList($categories));
|
return (bool) array_intersect($product_terms, $this->normalizeIdList($terms));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function productIsInIds($product, $ids) {
|
private function productIsInIds($product, $ids) {
|
||||||
@@ -214,7 +245,43 @@ class PricingService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return in_array($product->get_id(), $this->normalizeIdList($ids), true);
|
$ids = $this->normalizeIdList($ids);
|
||||||
|
$productIds = [(int) $product->get_id()];
|
||||||
|
if ($product->is_type('variation') && method_exists($product, 'get_parent_id')) {
|
||||||
|
$productIds[] = (int) $product->get_parent_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) array_intersect($productIds, $ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cartContainsProduct($ids) {
|
||||||
|
if (!function_exists('WC') || !WC()->cart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = $this->normalizeIdList($ids);
|
||||||
|
foreach (WC()->cart->get_cart() as $cartItem) {
|
||||||
|
if (in_array((int) $cartItem['product_id'], $ids, true) || in_array((int) $cartItem['variation_id'], $ids, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cartContainsCategory($categories) {
|
||||||
|
if (!function_exists('WC') || !WC()->cart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (WC()->cart->get_cart() as $cartItem) {
|
||||||
|
$product = wc_get_product($cartItem['product_id']);
|
||||||
|
if ($this->productHasCategory($product, $categories)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeIdList($value) {
|
private function normalizeIdList($value) {
|
||||||
@@ -249,7 +316,7 @@ class PricingService {
|
|||||||
if ($value <= 0) {
|
if ($value <= 0) {
|
||||||
return $price;
|
return $price;
|
||||||
}
|
}
|
||||||
return $price * (1 - $value / 100);
|
return $price * (1 - min(100, $value) / 100);
|
||||||
case 'discount_fixed':
|
case 'discount_fixed':
|
||||||
if ($value <= 0) {
|
if ($value <= 0) {
|
||||||
return $price;
|
return $price;
|
||||||
@@ -257,6 +324,10 @@ class PricingService {
|
|||||||
return $price - $value;
|
return $price - $value;
|
||||||
case 'set_price':
|
case 'set_price':
|
||||||
return $value > 0 ? $value : $price;
|
return $value > 0 ? $value : $price;
|
||||||
|
case 'increase_percent':
|
||||||
|
return $value > 0 ? $price * (1 + min(100, $value) / 100) : $price;
|
||||||
|
case 'increase_fixed':
|
||||||
|
return $value > 0 ? $price + $value : $price;
|
||||||
case 'free_shipping':
|
case 'free_shipping':
|
||||||
return $price;
|
return $price;
|
||||||
default:
|
default:
|
||||||
@@ -290,11 +361,78 @@ class PricingService {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function applyFreeShippingRates($rates) {
|
||||||
|
$rules = $this->getApplicableRules(null);
|
||||||
|
$hasFreeShippingRule = false;
|
||||||
|
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
foreach ($rule->actions as $action) {
|
||||||
|
if (($action['type'] ?? '') === 'free_shipping') {
|
||||||
|
$hasFreeShippingRule = true;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$hasFreeShippingRule) {
|
||||||
|
return $rates;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rates as $rate) {
|
||||||
|
$rate->cost = 0;
|
||||||
|
if (!empty($rate->taxes) && is_array($rate->taxes)) {
|
||||||
|
foreach ($rate->taxes as $taxId => $tax) {
|
||||||
|
$rate->taxes[$taxId] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function compareValue($actual, $expected, $operator) {
|
||||||
|
$expectedValues = array_map('trim', explode(',', (string) $expected));
|
||||||
|
$actual = (string) $actual;
|
||||||
|
|
||||||
|
switch ($operator) {
|
||||||
|
case 'is_not':
|
||||||
|
return $actual !== (string) $expected;
|
||||||
|
case 'in':
|
||||||
|
return in_array($actual, $expectedValues, true);
|
||||||
|
case 'not_in':
|
||||||
|
return !in_array($actual, $expectedValues, true);
|
||||||
|
case 'is':
|
||||||
|
default:
|
||||||
|
return $actual === (string) $expected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function compareAnyValue(array $actualValues, $expected, $operator) {
|
||||||
|
$expectedValues = array_map('trim', explode(',', (string) $expected));
|
||||||
|
|
||||||
|
if (in_array($operator, ['is_not', 'not_in'], true)) {
|
||||||
|
foreach ($actualValues as $actual) {
|
||||||
|
if (in_array((string) $actual, $expectedValues, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($actualValues as $actual) {
|
||||||
|
if (in_array((string) $actual, $expectedValues, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private function trackDiscountOnce($product, $oldPrice, $price, $ruleId) {
|
private function trackDiscountOnce($product, $oldPrice, $price, $ruleId) {
|
||||||
$productId = $product ? $product->get_id() : 0;
|
$productId = $product ? $product->get_id() : 0;
|
||||||
$key = implode(':', [$productId, (int) $ruleId, round($oldPrice, 4), round($price, 4)]);
|
$key = implode(':', [$productId, (int) $ruleId, round($oldPrice, 4), round($price, 4)]);
|
||||||
|
|
||||||
if (isset($this->trackedApplications[$key])) {
|
if ($price >= $oldPrice || isset($this->trackedApplications[$key])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,14 @@ add_filter('woocommerce_product_get_price', 'sodino_apply_dynamic_pricing', 10,
|
|||||||
add_filter('woocommerce_product_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
|
add_filter('woocommerce_product_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
|
||||||
add_filter('woocommerce_product_variation_get_price', 'sodino_apply_dynamic_pricing', 10, 2);
|
add_filter('woocommerce_product_variation_get_price', 'sodino_apply_dynamic_pricing', 10, 2);
|
||||||
add_filter('woocommerce_product_variation_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
|
add_filter('woocommerce_product_variation_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
|
||||||
|
add_filter('woocommerce_package_rates', 'sodino_apply_free_shipping_rules', 20, 1);
|
||||||
|
|
||||||
function sodino_apply_dynamic_pricing($price, $product) {
|
function sodino_apply_dynamic_pricing($price, $product) {
|
||||||
global $sodino_pricing_service;
|
global $sodino_pricing_service;
|
||||||
return $sodino_pricing_service->applyDynamicPricing($price, $product);
|
return $sodino_pricing_service->applyDynamicPricing($price, $product);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sodino_apply_free_shipping_rules($rates) {
|
||||||
|
global $sodino_pricing_service;
|
||||||
|
return $sodino_pricing_service->applyFreeShippingRates($rates);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user