feat(upsell): apply real cart discounts and track performance
This commit is contained in:
@@ -184,11 +184,11 @@ add_action('admin_menu', function() use ($adminController) {
|
|||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'sodino-dashboard',
|
'sodino-dashboard',
|
||||||
__('قیمت رقبا (بهزودی)', 'sodino'),
|
__('ابزارها و سلامت', 'sodino'),
|
||||||
__('قیمت رقبا (بهزودی)', 'sodino'),
|
__('ابزارها و سلامت', 'sodino'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'sodino-competitor-price',
|
'sodino-tools',
|
||||||
[$adminController, 'competitorPricePage']
|
[$adminController, 'toolsPage']
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
@@ -255,6 +255,11 @@ add_action('admin_init', function() use ($ruleController, $settingsController, $
|
|||||||
$settingsController->clearCache();
|
$settingsController->clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tools actions
|
||||||
|
if ($page === 'sodino-tools') {
|
||||||
|
$adminController->handleToolsActions();
|
||||||
|
}
|
||||||
|
|
||||||
// Banner actions
|
// Banner actions
|
||||||
if (strpos($page, 'sodino') === 0 && in_array($action, ['delete_banner', 'toggle_banner_status'], true)) {
|
if (strpos($page, 'sodino') === 0 && in_array($action, ['delete_banner', 'toggle_banner_status'], true)) {
|
||||||
$adminController->handleBannerActions();
|
$adminController->handleBannerActions();
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class Sodino_Upsell_List_Table extends WP_List_Table {
|
|||||||
'trigger' => __('شرط فعالسازی', 'sodino'),
|
'trigger' => __('شرط فعالسازی', 'sodino'),
|
||||||
'suggested_product'=> __('محصول پیشنهادی', 'sodino'),
|
'suggested_product'=> __('محصول پیشنهادی', 'sodino'),
|
||||||
'discount' => __('تخفیف', 'sodino'),
|
'discount' => __('تخفیف', 'sodino'),
|
||||||
|
'performance' => __('عملکرد', 'sodino'),
|
||||||
'status' => __('وضعیت', 'sodino'),
|
'status' => __('وضعیت', 'sodino'),
|
||||||
'actions' => __('عملیات', 'sodino'),
|
'actions' => __('عملیات', 'sodino'),
|
||||||
];
|
];
|
||||||
@@ -86,6 +87,22 @@ class Sodino_Upsell_List_Table extends WP_List_Table {
|
|||||||
return __('بدون تخفیف', 'sodino');
|
return __('بدون تخفیف', 'sodino');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function column_performance($item) {
|
||||||
|
$impressions = max(0, (int) ($item->impressions ?? 0));
|
||||||
|
$conversions = max(0, (int) ($item->conversions ?? 0));
|
||||||
|
$rate = $impressions > 0 ? round(($conversions / $impressions) * 100, 2) : 0;
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s: %s<br>%s: %s<br>%s: %s%%',
|
||||||
|
esc_html__('نمایش', 'sodino'),
|
||||||
|
esc_html(number_format_i18n($impressions)),
|
||||||
|
esc_html__('افزودن', 'sodino'),
|
||||||
|
esc_html(number_format_i18n($conversions)),
|
||||||
|
esc_html__('نرخ', 'sodino'),
|
||||||
|
esc_html(number_format_i18n($rate, 2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function column_status($item) {
|
public function column_status($item) {
|
||||||
return $item->status ? __('فعال', 'sodino') : __('غیرفعال', 'sodino');
|
return $item->status ? __('فعال', 'sodino') : __('غیرفعال', 'sodino');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $menu_items = [
|
|||||||
'sodino-add-upsell' => __('افزودن آپسل', 'sodino'),
|
'sodino-add-upsell' => __('افزودن آپسل', 'sodino'),
|
||||||
'sodino-banners' => __('بنرهای هوشمند', 'sodino'),
|
'sodino-banners' => __('بنرهای هوشمند', 'sodino'),
|
||||||
'sodino-add-banner' => __('افزودن بنر', 'sodino'),
|
'sodino-add-banner' => __('افزودن بنر', 'sodino'),
|
||||||
'sodino-competitor-price' => __('قیمت رقبا (بهزودی)', 'sodino'),
|
'sodino-tools' => __('ابزارها و سلامت', 'sodino'),
|
||||||
'sodino-settings' => __('تنظیمات', 'sodino'),
|
'sodino-settings' => __('تنظیمات', 'sodino'),
|
||||||
];
|
];
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ $form_display_type = function_exists('sodino_old_input') ? sodino_old_input('dis
|
|||||||
<a href="<?php echo admin_url('admin.php?page=sodino-add-banner'); ?>" class="block px-3 py-2 rounded-xl 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'; ?>">
|
<a href="<?php echo admin_url('admin.php?page=sodino-add-banner'); ?>" class="block px-3 py-2 rounded-xl 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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-xl text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<a href="<?php echo admin_url('admin.php?page=sodino-tools'); ?>" class="block px-3 py-2 rounded-xl 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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-settings'); ?>" class="block px-3 py-2 rounded-xl 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'; ?>">
|
<a href="<?php echo admin_url('admin.php?page=sodino-settings'); ?>" class="block px-3 py-2 rounded-xl 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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ $current_page = sanitize_text_field($_GET['page'] ?? 'sodino-banners');
|
|||||||
<a href="<?php echo admin_url('admin.php?page=sodino-add-banner'); ?>" class="block px-3 py-2 rounded-xl 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'; ?>">
|
<a href="<?php echo admin_url('admin.php?page=sodino-add-banner'); ?>" class="block px-3 py-2 rounded-xl 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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-xl text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<a href="<?php echo admin_url('admin.php?page=sodino-tools'); ?>" class="block px-3 py-2 rounded-xl 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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-settings'); ?>" class="block px-3 py-2 rounded-xl 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'; ?>">
|
<a href="<?php echo admin_url('admin.php?page=sodino-settings'); ?>" class="block px-3 py-2 rounded-xl 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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
@@ -1,152 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Prevent direct access
|
|
||||||
if (!defined('ABSPATH')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-competitor-price');
|
|
||||||
?>
|
|
||||||
<div id="sodino-app" class="min-h-screen bg-gray-50" dir="rtl">
|
|
||||||
<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="py-6">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<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 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(); } ?>
|
|
||||||
<div class="flex gap-8">
|
|
||||||
<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-competitor-price'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? '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 class="flex-1 min-w-0">
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
|
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 class="text-2xl font-semibold text-gray-900"><?php _e('قیمت رقبا', 'sodino'); ?></h2>
|
|
||||||
<p class="mt-2 text-gray-600"><?php _e('بهزودی بخش مانیتورینگ قیمت رقبا فعال خواهد شد.', 'sodino'); ?></p>
|
|
||||||
</div>
|
|
||||||
<span class="inline-flex items-center rounded-full bg-yellow-50 px-3 py-1 text-sm font-medium text-yellow-700"><?php _e('بهزودی', 'sodino'); ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-6 lg:grid-cols-3">
|
|
||||||
<div class="lg:col-span-2 bg-white rounded-2xl border border-gray-200 shadow-sm p-6">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4"><?php _e('مشخصات رقیب', 'sodino'); ?></h3>
|
|
||||||
<p class="text-sm text-gray-500 mb-6"><?php _e('این قابلیت به شما امکان میدهد قیمت محصولات خود را با رقبا مقایسه کرده و بهینهسازی کنید. (در نسخههای آینده فعال خواهد شد)', 'sodino'); ?></p>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto rounded-xl border border-gray-200">
|
|
||||||
<table class="min-w-full divide-y divide-gray-200 text-right text-sm text-gray-700">
|
|
||||||
<thead class="bg-gray-50 text-gray-500">
|
|
||||||
<tr>
|
|
||||||
<th class="px-4 py-3 font-medium"><?php _e('محصول', 'sodino'); ?></th>
|
|
||||||
<th class="px-4 py-3 font-medium"><?php _e('قیمت شما', 'sodino'); ?></th>
|
|
||||||
<th class="px-4 py-3 font-medium"><?php _e('قیمت رقبا', 'sodino'); ?></th>
|
|
||||||
<th class="px-4 py-3 font-medium"><?php _e('اختلاف قیمت', 'sodino'); ?></th>
|
|
||||||
<th class="px-4 py-3 font-medium"><?php _e('وضعیت', 'sodino'); ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-200 bg-white">
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 py-3"><?php _e('کفش دویدن', 'sodino'); ?></td>
|
|
||||||
<td class="px-4 py-3">23,500 تومان</td>
|
|
||||||
<td class="px-4 py-3">24,900 تومان</td>
|
|
||||||
<td class="px-4 py-3 text-green-600">1,400 تومان کمتر</td>
|
|
||||||
<td class="px-4 py-3"><span class="rounded-full bg-green-50 px-3 py-1 text-xs font-semibold text-green-700"><?php _e('رقبتی', 'sodino'); ?></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 py-3"><?php _e('کرم محافظ پوست', 'sodino'); ?></td>
|
|
||||||
<td class="px-4 py-3">78,000 تومان</td>
|
|
||||||
<td class="px-4 py-3">82,000 تومان</td>
|
|
||||||
<td class="px-4 py-3 text-green-600">4,000 تومان کمتر</td>
|
|
||||||
<td class="px-4 py-3"><span class="rounded-full bg-green-50 px-3 py-1 text-xs font-semibold text-green-700"><?php _e('رقبتی', 'sodino'); ?></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 py-3"><?php _e('کیف دستی', 'sodino'); ?></td>
|
|
||||||
<td class="px-4 py-3">155,000 تومان</td>
|
|
||||||
<td class="px-4 py-3">161,000 تومان</td>
|
|
||||||
<td class="px-4 py-3 text-red-600">6,000 تومان بیشتر</td>
|
|
||||||
<td class="px-4 py-3"><span class="rounded-full bg-gray-50 px-3 py-1 text-xs font-semibold text-gray-700"><?php _e('هشدار', 'sodino'); ?></span></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div class="bg-white rounded-2xl border border-gray-200 shadow-sm p-6">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900"><?php _e('افزودن رقیب', 'sodino'); ?></h3>
|
|
||||||
<span class="rounded-full bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700"><?php _e('بهزودی', 'sodino'); ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2"><?php _e('URL سایت رقیب', 'sodino'); ?></label>
|
|
||||||
<input type="url" disabled class="w-full rounded-lg border border-gray-200 bg-gray-100 px-4 py-3 text-gray-500 shadow-sm" placeholder="https://example.com">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2"><?php _e('نام فروشگاه', 'sodino'); ?></label>
|
|
||||||
<input type="text" disabled class="w-full rounded-lg border border-gray-200 bg-gray-100 px-4 py-3 text-gray-500 shadow-sm" placeholder="<?php _e('مثلاً اسم فروشگاه رقبا', 'sodino'); ?>">
|
|
||||||
</div>
|
|
||||||
<button disabled class="w-full rounded-full bg-gray-300 px-4 py-3 text-sm font-semibold text-gray-700"><?php _e('افزودن', 'sodino'); ?></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white rounded-2xl border border-gray-200 shadow-sm p-6">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<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>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<span class="px-3 py-1 rounded-full bg-gray-100 text-xs font-semibold text-gray-600"><?php _e('غیرفعال', 'sodino'); ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-2xl border border-gray-200 bg-gray-50 p-5 text-sm text-gray-500">
|
|
||||||
<?php _e('در اینجا میتوانید فعالسازی خودکار تحلیل قیمت رقبا را مشاهده کنید؛ در حال حاضر این بخش در دست ساخت است.', 'sodino'); ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -55,8 +55,8 @@ $current_page = sanitize_text_field($_GET['page'] ?? 'sodino-dashboard');
|
|||||||
<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'; ?>">
|
<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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</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'; ?>">
|
<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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ $form_action_type = function_exists('sodino_old_input') ? sodino_old_input('acti
|
|||||||
<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'; ?>">
|
<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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</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'; ?>">
|
<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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ $current_page = sanitize_text_field($_GET['page'] ?? 'sodino-rules');
|
|||||||
<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'; ?>">
|
<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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</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'; ?>">
|
<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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
131
admin/views/tools.php
Normal file
131
admin/views/tools.php
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
// Prevent direct access
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-tools');
|
||||||
|
$toolsData = $toolsData ?? [];
|
||||||
|
$tables = $toolsData['tables'] ?? [];
|
||||||
|
$actions = $toolsData['actions'] ?? [];
|
||||||
|
$settings = $toolsData['settings'] ?? [];
|
||||||
|
$db_version = $toolsData['db_version'] ?? '0';
|
||||||
|
$expected_db_version = $toolsData['expected_db_version'] ?? SODINO_VERSION;
|
||||||
|
$old_event_count = (int) ($toolsData['old_event_count'] ?? 0);
|
||||||
|
$oldest_event = $toolsData['oldest_event'] ?? '';
|
||||||
|
?>
|
||||||
|
<div id="sodino-app" class="min-h-screen bg-gray-50" dir="rtl">
|
||||||
|
<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="py-6">
|
||||||
|
<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 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(); } ?>
|
||||||
|
<div class="flex gap-8">
|
||||||
|
<?php include SODINO_PLUGIN_DIR . 'admin/components/sidebar.php'; ?>
|
||||||
|
|
||||||
|
<main class="flex-1 min-w-0 space-y-6">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-semibold text-gray-900"><?php _e('ابزارها و سلامت', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-2 text-gray-600"><?php _e('وضعیت دیتابیس، کش و دادههای تحلیلی سودینو را از اینجا مدیریت کنید.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<a href="<?php echo esc_url($actions['clear_cache'] ?? '#'); ?>" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700">
|
||||||
|
<?php _e('پاکسازی کش', 'sodino'); ?>
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo esc_url($actions['run_migrations'] ?? '#'); ?>" 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">
|
||||||
|
<?php _e('بررسی دیتابیس', 'sodino'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-3">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<p class="text-sm font-medium text-gray-500"><?php _e('نسخه دیتابیس', 'sodino'); ?></p>
|
||||||
|
<div class="mt-3 flex items-center justify-between">
|
||||||
|
<strong class="text-2xl text-gray-900"><?php echo esc_html($db_version); ?></strong>
|
||||||
|
<?php if (version_compare($db_version, $expected_db_version, '>=')) : ?>
|
||||||
|
<span class="rounded-full bg-green-50 px-3 py-1 text-xs font-semibold text-green-700"><?php _e('بهروز', 'sodino'); ?></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="rounded-full bg-yellow-50 px-3 py-1 text-xs font-semibold text-yellow-700"><?php _e('نیازمند بررسی', 'sodino'); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<p class="text-sm font-medium text-gray-500"><?php _e('رویدادهای تحلیلی', 'sodino'); ?></p>
|
||||||
|
<strong class="mt-3 block text-2xl text-gray-900"><?php echo esc_html(number_format_i18n($tables['events']['count'] ?? 0)); ?></strong>
|
||||||
|
<p class="mt-2 text-sm text-gray-500">
|
||||||
|
<?php echo $oldest_event ? esc_html(sprintf(__('قدیمیترین رویداد: %s', 'sodino'), mysql2date('Y/m/d H:i', $oldest_event))) : esc_html__('هنوز رویدادی ثبت نشده است.', 'sodino'); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<p class="text-sm font-medium text-gray-500"><?php _e('وضعیت قابلیتها', 'sodino'); ?></p>
|
||||||
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<span class="rounded-full px-3 py-1 text-xs font-semibold <?php echo !empty($settings['pricing_enabled']) ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'; ?>"><?php _e('قیمتگذاری', 'sodino'); ?></span>
|
||||||
|
<span class="rounded-full px-3 py-1 text-xs font-semibold <?php echo !empty($settings['upsell_enabled']) ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'; ?>"><?php _e('آپسل', 'sodino'); ?></span>
|
||||||
|
<span class="rounded-full px-3 py-1 text-xs font-semibold <?php echo !empty($settings['banner_enabled']) ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'; ?>"><?php _e('بنر', 'sodino'); ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="px-6 py-5 border-b border-gray-200">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900"><?php _e('جدولهای سودینو', 'sodino'); ?></h3>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 text-right text-sm">
|
||||||
|
<thead class="bg-gray-50 text-gray-500">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 font-medium"><?php _e('بخش', 'sodino'); ?></th>
|
||||||
|
<th class="px-6 py-3 font-medium"><?php _e('جدول', 'sodino'); ?></th>
|
||||||
|
<th class="px-6 py-3 font-medium"><?php _e('تعداد رکورد', 'sodino'); ?></th>
|
||||||
|
<th class="px-6 py-3 font-medium"><?php _e('وضعیت', 'sodino'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200 bg-white text-gray-700">
|
||||||
|
<?php foreach ($tables as $table) : ?>
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 font-medium text-gray-900"><?php echo esc_html($table['label']); ?></td>
|
||||||
|
<td class="px-6 py-4"><code><?php echo esc_html($table['name']); ?></code></td>
|
||||||
|
<td class="px-6 py-4"><?php echo esc_html(number_format_i18n($table['count'])); ?></td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<?php if (!empty($table['exists'])) : ?>
|
||||||
|
<span class="rounded-full bg-green-50 px-3 py-1 text-xs font-semibold text-green-700"><?php _e('موجود', 'sodino'); ?></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="rounded-full bg-red-50 px-3 py-1 text-xs font-semibold text-red-700"><?php _e('نیازمند ساخت', 'sodino'); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900"><?php _e('پاکسازی دادههای قدیمی', 'sodino'); ?></h3>
|
||||||
|
<p class="mt-2 text-sm text-gray-500">
|
||||||
|
<?php echo esc_html(sprintf(__('رویدادهای قدیمیتر از ۹۰ روز آماده حذف: %s', 'sodino'), number_format_i18n($old_event_count))); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="<?php echo esc_url($actions['prune_events'] ?? '#'); ?>" onclick="return confirm('<?php echo esc_js(__('رویدادهای قدیمی حذف شوند؟ این عملیات قابل بازگشت نیست.', 'sodino')); ?>');" class="inline-flex items-center justify-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-semibold text-red-700 hover:bg-red-100">
|
||||||
|
<?php _e('حذف رویدادهای قدیمی', 'sodino'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -69,8 +69,8 @@ $product_categories = get_terms([
|
|||||||
<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'; ?>">
|
<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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</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'; ?>">
|
<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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ $current_page = sanitize_text_field($_GET['page'] ?? 'sodino-upsells');
|
|||||||
<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'; ?>">
|
<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'); ?>
|
<?php _e('افزودن بنر', 'sodino'); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo admin_url('admin.php?page=sodino-competitor-price'); ?>" class="block px-3 py-2 rounded-md text-sm font-medium <?php echo $current_page === 'sodino-competitor-price' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>">
|
<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'); ?>
|
<?php _e('ابزارها و سلامت', 'sodino'); ?>
|
||||||
</a>
|
</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'; ?>">
|
<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'); ?>
|
<?php _e('تنظیمات', 'sodino'); ?>
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ class AdminController {
|
|||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'sodino-rules',
|
'sodino-rules',
|
||||||
__('قیمت رقبا (بهزودی)', 'sodino'),
|
__('ابزارها و سلامت', 'sodino'),
|
||||||
__('قیمت رقبا (بهزودی)', 'sodino'),
|
__('ابزارها و سلامت', 'sodino'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'sodino-competitor-price',
|
'sodino-tools',
|
||||||
[$this, 'competitorPricePage']
|
[$this, 'toolsPage']
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
@@ -295,10 +295,49 @@ class AdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Competitor price page
|
* Tools and health page
|
||||||
*/
|
*/
|
||||||
public function competitorPricePage() {
|
public function toolsPage() {
|
||||||
include SODINO_PLUGIN_DIR . 'admin/views/competitor-price.php';
|
$toolsData = $this->getToolsData();
|
||||||
|
include SODINO_PLUGIN_DIR . 'admin/views/tools.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleToolsActions() {
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($_GET['page'] ?? '') !== 'sodino-tools' || empty($_GET['tool_action'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = sanitize_key($_GET['tool_action']);
|
||||||
|
if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'sodino_tools_' . $action)) {
|
||||||
|
wp_die(__('خطای امنیتی رخ داد.', 'sodino'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'clear_cache') {
|
||||||
|
\Sodino\Core\Cache::getInstance()->clearAll();
|
||||||
|
$this->redirectWithNotice(admin_url('admin.php?page=sodino-tools'), __('کش سودینو با موفقیت پاک شد.', 'sodino'), 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'run_migrations') {
|
||||||
|
require_once SODINO_PLUGIN_DIR . 'database/migrations.php';
|
||||||
|
sodino_create_tables();
|
||||||
|
$this->redirectWithNotice(admin_url('admin.php?page=sodino-tools'), __('ساختار دیتابیس سودینو بررسی و بهروزرسانی شد.', 'sodino'), 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'prune_events') {
|
||||||
|
$deleted = $this->deleteOldEvents(90);
|
||||||
|
$this->redirectWithNotice(
|
||||||
|
admin_url('admin.php?page=sodino-tools'),
|
||||||
|
sprintf(__('پاکسازی انجام شد. %d رویداد قدیمی حذف شد.', 'sodino'), $deleted),
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_safe_redirect(admin_url('admin.php?page=sodino-tools'));
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function listUpsellsPage() {
|
private function listUpsellsPage() {
|
||||||
@@ -385,6 +424,9 @@ class AdminController {
|
|||||||
$upsell->target_product_id = max(0, intval($_POST['target_product_id'] ?? 0));
|
$upsell->target_product_id = max(0, intval($_POST['target_product_id'] ?? 0));
|
||||||
$upsell->discount_type = $discountType;
|
$upsell->discount_type = $discountType;
|
||||||
$upsell->discount_value = max(0, floatval($_POST['discount_value'] ?? 0));
|
$upsell->discount_value = max(0, floatval($_POST['discount_value'] ?? 0));
|
||||||
|
if ($discountType === 'percentage') {
|
||||||
|
$upsell->discount_value = min(100, $upsell->discount_value);
|
||||||
|
}
|
||||||
$upsell->priority = max(1, intval($_POST['priority'] ?? 10));
|
$upsell->priority = max(1, intval($_POST['priority'] ?? 10));
|
||||||
$upsell->status = isset($_POST['status']) ? 1 : 0;
|
$upsell->status = isset($_POST['status']) ? 1 : 0;
|
||||||
|
|
||||||
@@ -551,6 +593,80 @@ class AdminController {
|
|||||||
wp_send_json($results);
|
wp_send_json($results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getToolsData() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$tables = [
|
||||||
|
'rules' => [
|
||||||
|
'label' => __('قوانین قیمتگذاری', 'sodino'),
|
||||||
|
'name' => $wpdb->prefix . 'sodino_rules',
|
||||||
|
],
|
||||||
|
'upsells' => [
|
||||||
|
'label' => __('آپسلها', 'sodino'),
|
||||||
|
'name' => $wpdb->prefix . 'sodino_upsells',
|
||||||
|
],
|
||||||
|
'banners' => [
|
||||||
|
'label' => __('بنرها', 'sodino'),
|
||||||
|
'name' => $wpdb->prefix . 'sodino_banners',
|
||||||
|
],
|
||||||
|
'events' => [
|
||||||
|
'label' => __('رویدادهای تحلیلی', 'sodino'),
|
||||||
|
'name' => $wpdb->prefix . 'sodino_events',
|
||||||
|
],
|
||||||
|
'analytics_cache' => [
|
||||||
|
'label' => __('کش تحلیلی', 'sodino'),
|
||||||
|
'name' => $wpdb->prefix . 'sodino_analytics_cache',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($tables as $key => $table) {
|
||||||
|
$exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table['name']));
|
||||||
|
$tables[$key]['exists'] = (bool) $exists;
|
||||||
|
$tables[$key]['count'] = $exists ? (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table['name']}") : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventsTable = $tables['events']['name'];
|
||||||
|
$oldEventCount = 0;
|
||||||
|
$oldestEvent = '';
|
||||||
|
if ($tables['events']['exists']) {
|
||||||
|
$cutoff = date('Y-m-d H:i:s', current_time('timestamp') - (90 * DAY_IN_SECONDS));
|
||||||
|
$oldEventCount = (int) $wpdb->get_var(
|
||||||
|
$wpdb->prepare("SELECT COUNT(*) FROM {$eventsTable} WHERE created_at < %s", $cutoff)
|
||||||
|
);
|
||||||
|
$oldestEvent = (string) $wpdb->get_var("SELECT MIN(created_at) FROM {$eventsTable}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'db_version' => get_option('sodino_db_version', '0'),
|
||||||
|
'expected_db_version' => defined('SODINO_DB_VERSION') ? SODINO_DB_VERSION : SODINO_VERSION,
|
||||||
|
'settings' => $this->getSettings(),
|
||||||
|
'tables' => $tables,
|
||||||
|
'old_event_count' => $oldEventCount,
|
||||||
|
'oldest_event' => $oldestEvent,
|
||||||
|
'actions' => [
|
||||||
|
'clear_cache' => wp_nonce_url(admin_url('admin.php?page=sodino-tools&tool_action=clear_cache'), 'sodino_tools_clear_cache'),
|
||||||
|
'run_migrations' => wp_nonce_url(admin_url('admin.php?page=sodino-tools&tool_action=run_migrations'), 'sodino_tools_run_migrations'),
|
||||||
|
'prune_events' => wp_nonce_url(admin_url('admin.php?page=sodino-tools&tool_action=prune_events'), 'sodino_tools_prune_events'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteOldEvents($days) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$days = max(1, (int) $days);
|
||||||
|
$eventsTable = $wpdb->prefix . 'sodino_events';
|
||||||
|
$exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $eventsTable));
|
||||||
|
if (!$exists) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cutoff = date('Y-m-d H:i:s', current_time('timestamp') - ($days * DAY_IN_SECONDS));
|
||||||
|
$deleted = $wpdb->query($wpdb->prepare("DELETE FROM {$eventsTable} WHERE created_at < %s", $cutoff));
|
||||||
|
|
||||||
|
return $deleted === false ? 0 : (int) $deleted;
|
||||||
|
}
|
||||||
|
|
||||||
private function getSettingsDefaults() {
|
private function getSettingsDefaults() {
|
||||||
return [
|
return [
|
||||||
'plugin_enabled' => 1,
|
'plugin_enabled' => 1,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class Upsell {
|
|||||||
public $discount_value;
|
public $discount_value;
|
||||||
public $status;
|
public $status;
|
||||||
public $priority;
|
public $priority;
|
||||||
|
public $impressions;
|
||||||
|
public $conversions;
|
||||||
public $created_at;
|
public $created_at;
|
||||||
public $updated_at;
|
public $updated_at;
|
||||||
|
|
||||||
@@ -27,6 +29,8 @@ class Upsell {
|
|||||||
$this->discount_value = isset($data['discount_value']) ? floatval($data['discount_value']) : 0;
|
$this->discount_value = isset($data['discount_value']) ? floatval($data['discount_value']) : 0;
|
||||||
$this->status = isset($data['status']) ? (int) $data['status'] : 1;
|
$this->status = isset($data['status']) ? (int) $data['status'] : 1;
|
||||||
$this->priority = isset($data['priority']) ? (int) $data['priority'] : 10;
|
$this->priority = isset($data['priority']) ? (int) $data['priority'] : 10;
|
||||||
|
$this->impressions = isset($data['impressions']) ? (int) $data['impressions'] : 0;
|
||||||
|
$this->conversions = isset($data['conversions']) ? (int) $data['conversions'] : 0;
|
||||||
$this->created_at = $data['created_at'] ?? null;
|
$this->created_at = $data['created_at'] ?? null;
|
||||||
$this->updated_at = $data['updated_at'] ?? null;
|
$this->updated_at = $data['updated_at'] ?? null;
|
||||||
}
|
}
|
||||||
@@ -46,6 +50,8 @@ class Upsell {
|
|||||||
'discount_value' => floatval($this->discount_value),
|
'discount_value' => floatval($this->discount_value),
|
||||||
'status' => $this->status,
|
'status' => $this->status,
|
||||||
'priority' => $this->priority,
|
'priority' => $this->priority,
|
||||||
|
'impressions' => $this->impressions,
|
||||||
|
'conversions' => $this->conversions,
|
||||||
'created_at' => $this->created_at,
|
'created_at' => $this->created_at,
|
||||||
'updated_at' => $this->updated_at,
|
'updated_at' => $this->updated_at,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -66,4 +66,18 @@ class UpsellRepository {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
return $wpdb->delete($this->table_name, ['id' => $id]);
|
return $wpdb->delete($this->table_name, ['id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function incrementImpression($id) {
|
||||||
|
global $wpdb;
|
||||||
|
return $wpdb->query(
|
||||||
|
$wpdb->prepare("UPDATE {$this->table_name} SET impressions = impressions + 1 WHERE id = %d", $id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incrementConversion($id) {
|
||||||
|
global $wpdb;
|
||||||
|
return $wpdb->query(
|
||||||
|
$wpdb->prepare("UPDATE {$this->table_name} SET conversions = conversions + 1 WHERE id = %d", $id)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,18 @@ class UpsellService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$price = floatval($product->get_price());
|
$price = floatval($product->get_price());
|
||||||
|
return $this->calculateDiscountedPrice($price, $upsell);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function calculateDiscountedPrice($price, $upsell) {
|
||||||
|
$price = max(0, floatval($price));
|
||||||
|
if (!$upsell) {
|
||||||
|
return $price;
|
||||||
|
}
|
||||||
|
|
||||||
if ($upsell->discount_type === 'percentage') {
|
if ($upsell->discount_type === 'percentage') {
|
||||||
return max(0, $price * (1 - floatval($upsell->discount_value) / 100));
|
$percent = max(0, min(100, floatval($upsell->discount_value)));
|
||||||
|
return max(0, $price * (1 - $percent / 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($upsell->discount_type === 'fixed') {
|
if ($upsell->discount_type === 'fixed') {
|
||||||
@@ -54,6 +64,31 @@ class UpsellService {
|
|||||||
return $price;
|
return $price;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getById($id) {
|
||||||
|
return $this->upsellRepository->getById((int) $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValidForCart($upsell, $cart) {
|
||||||
|
return $cart && !$cart->is_empty() && $this->cartMatchesTrigger($upsell, $cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canApplyToProduct($upsell, $productId, $variationId = 0) {
|
||||||
|
if (!$upsell || !$upsell->isActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetProductId = (int) $upsell->target_product_id;
|
||||||
|
return $targetProductId > 0 && ($targetProductId === (int) $productId || $targetProductId === (int) $variationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incrementImpression($upsellId) {
|
||||||
|
return $this->upsellRepository->incrementImpression((int) $upsellId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incrementConversion($upsellId) {
|
||||||
|
return $this->upsellRepository->incrementConversion((int) $upsellId);
|
||||||
|
}
|
||||||
|
|
||||||
public function getTriggerLabel($upsell) {
|
public function getTriggerLabel($upsell) {
|
||||||
switch ($upsell->trigger_type) {
|
switch ($upsell->trigger_type) {
|
||||||
case 'product':
|
case 'product':
|
||||||
|
|||||||
@@ -214,5 +214,16 @@ function sodino_run_migrations($from_version) {
|
|||||||
if ($has_column($rules_table, 'actions')) {
|
if ($has_column($rules_table, 'actions')) {
|
||||||
$wpdb->query("UPDATE {$rules_table} SET actions = '[]' WHERE actions IS NULL OR actions = ''");
|
$wpdb->query("UPDATE {$rules_table} SET actions = '[]' WHERE actions IS NULL OR actions = ''");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$upsell_table = $wpdb->prefix . 'sodino_upsells';
|
||||||
|
if ($has_column($upsell_table, 'id')) {
|
||||||
|
if (!$has_column($upsell_table, 'impressions')) {
|
||||||
|
$wpdb->query("ALTER TABLE {$upsell_table} ADD COLUMN impressions bigint(20) NOT NULL DEFAULT 0 AFTER priority");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$has_column($upsell_table, 'conversions')) {
|
||||||
|
$wpdb->query("ALTER TABLE {$upsell_table} ADD COLUMN conversions bigint(20) NOT NULL DEFAULT 0 AFTER impressions");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
213
public/css/upsell-frontend.css
Normal file
213
public/css/upsell-frontend.css
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
.sodino-upsell-panel {
|
||||||
|
direction: rtl;
|
||||||
|
margin: 0 0 28px;
|
||||||
|
padding: 22px;
|
||||||
|
color: #172033;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 18px 42px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-panel *,
|
||||||
|
.sodino-upsell-panel *::before,
|
||||||
|
.sodino-upsell-panel *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-eyebrow {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.45;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-count {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
color: #1d4ed8;
|
||||||
|
background: #eff6ff;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 96px minmax(0, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 168px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.1);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-media {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-body {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-meta span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 4px 9px;
|
||||||
|
color: #047857;
|
||||||
|
background: #ecfdf5;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-meta span + span {
|
||||||
|
color: #475569;
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-title {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-card h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.45;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-price {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-price del {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-price small {
|
||||||
|
color: #047857;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: #ffffff !important;
|
||||||
|
background: #2563eb;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: background-color 160ms ease, transform 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-button:hover,
|
||||||
|
.sodino-upsell-button:focus {
|
||||||
|
color: #ffffff !important;
|
||||||
|
background: #1d4ed8;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.sodino-upsell-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.sodino-upsell-panel {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-header,
|
||||||
|
.sodino-upsell-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-card {
|
||||||
|
grid-template-columns: 76px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-media {
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-upsell-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,21 @@ $upsellRepository = new UpsellRepository();
|
|||||||
$sodino_upsell_service = new UpsellService($upsellRepository);
|
$sodino_upsell_service = new UpsellService($upsellRepository);
|
||||||
|
|
||||||
add_action('woocommerce_before_cart', 'sodino_render_upsell_suggestions');
|
add_action('woocommerce_before_cart', 'sodino_render_upsell_suggestions');
|
||||||
|
add_action('wp_enqueue_scripts', 'sodino_enqueue_upsell_assets');
|
||||||
|
add_filter('woocommerce_add_cart_item_data', 'sodino_add_upsell_cart_item_data', 10, 4);
|
||||||
|
add_filter('woocommerce_get_cart_item_from_session', 'sodino_restore_upsell_cart_item_data', 10, 2);
|
||||||
|
add_action('woocommerce_before_calculate_totals', 'sodino_apply_upsell_cart_prices', 20);
|
||||||
|
add_filter('woocommerce_get_item_data', 'sodino_display_upsell_cart_item_data', 10, 2);
|
||||||
|
add_action('woocommerce_checkout_create_order_line_item', 'sodino_add_upsell_order_item_meta', 10, 4);
|
||||||
|
add_action('woocommerce_add_to_cart', 'sodino_track_upsell_conversion', 20, 6);
|
||||||
|
|
||||||
|
function sodino_enqueue_upsell_assets() {
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style('sodino-upsell-frontend', plugin_dir_url(__FILE__) . '../css/upsell-frontend.css', [], SODINO_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
function sodino_render_upsell_suggestions() {
|
function sodino_render_upsell_suggestions() {
|
||||||
if (is_admin() || !is_cart()) {
|
if (is_admin() || !is_cart()) {
|
||||||
@@ -41,19 +56,19 @@ function sodino_render_upsell_suggestions() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '<div class="sodino-upsell-panel bg-white rounded-2xl border border-gray-200 p-6 mb-8 shadow-sm">';
|
echo '<section class="sodino-upsell-panel" aria-label="' . esc_attr__('پیشنهادهای ویژه سودینو', 'sodino') . '">';
|
||||||
echo '<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">';
|
echo '<div class="sodino-upsell-header">';
|
||||||
echo '<div>';
|
echo '<div>';
|
||||||
echo '<p class="text-sm font-semibold text-blue-600">' . esc_html__('پیشنهاد ویژه آپسل', 'sodino') . '</p>';
|
echo '<p class="sodino-upsell-eyebrow">' . esc_html__('پیشنهاد ویژه', 'sodino') . '</p>';
|
||||||
echo '<h2 class="mt-2 text-xl font-bold text-gray-900">' . esc_html__('این محصول را همراه خرید خود با تخفیف ویژه دریافت کنید', 'sodino') . '</h2>';
|
echo '<h2>' . esc_html__('این پیشنهادها را با تخفیف به سبد خود اضافه کنید', 'sodino') . '</h2>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '<span class="inline-flex items-center rounded-full bg-blue-50 px-3 py-1 text-sm font-medium text-blue-700">' . count($upsells) . ' ' . esc_html__('پیشنهاد فعال', 'sodino') . '</span>';
|
echo '<span class="sodino-upsell-count">' . esc_html(sprintf(__('%d پیشنهاد فعال', 'sodino'), count($upsells))) . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '<div class="mt-6 grid gap-4 lg:grid-cols-'.min(2, count($upsells)).'">';
|
echo '<div class="sodino-upsell-grid">';
|
||||||
|
|
||||||
foreach ($upsells as $upsell) {
|
foreach ($upsells as $upsell) {
|
||||||
$product = wc_get_product($upsell->target_product_id);
|
$product = wc_get_product($upsell->target_product_id);
|
||||||
if (!$product) {
|
if (!$product || !$product->is_purchasable() || !$product->is_in_stock() || !$product->is_type(['simple', 'variation'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,31 +76,161 @@ function sodino_render_upsell_suggestions() {
|
|||||||
$originalPrice = floatval($product->get_price());
|
$originalPrice = floatval($product->get_price());
|
||||||
$priceHtml = wc_price($discountedPrice);
|
$priceHtml = wc_price($discountedPrice);
|
||||||
if ($discountedPrice < $originalPrice) {
|
if ($discountedPrice < $originalPrice) {
|
||||||
$priceHtml .= ' <span class="mr-2 text-sm text-gray-500 line-through">' . wc_price($originalPrice) . '</span>';
|
$priceHtml .= ' <del>' . wc_price($originalPrice) . '</del>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$addToCartUrl = esc_url(add_query_arg('add-to-cart', $product->get_id(), wc_get_cart_url()));
|
$addToCartUrl = sodino_get_upsell_add_to_cart_url($product, $upsell);
|
||||||
$image = $product->get_image('woocommerce_thumbnail', ['class' => 'h-20 w-20 rounded-xl object-cover']);
|
$image = $product->get_image('woocommerce_thumbnail', ['class' => 'sodino-upsell-image']);
|
||||||
|
$savings = max(0, $originalPrice - $discountedPrice);
|
||||||
|
$sodino_upsell_service->incrementImpression($upsell->id);
|
||||||
|
|
||||||
echo '<div class="rounded-2xl border border-gray-200 p-5 bg-gray-50">';
|
echo '<article class="sodino-upsell-card">';
|
||||||
echo '<div class="flex gap-4">';
|
echo '<div class="sodino-upsell-media">' . $image . '</div>';
|
||||||
echo '<div class="flex-shrink-0">' . $image . '</div>';
|
echo '<div class="sodino-upsell-body">';
|
||||||
echo '<div class="flex-1">';
|
echo '<div class="sodino-upsell-meta">';
|
||||||
echo '<p class="text-sm font-medium text-gray-700">' . esc_html($upsell->title) . '</p>';
|
echo '<span>' . esc_html($sodino_upsell_service->getDiscountLabel($upsell)) . '</span>';
|
||||||
echo '<h3 class="mt-2 text-lg font-semibold text-gray-900">' . esc_html($product->get_name()) . '</h3>';
|
echo '<span>' . esc_html($sodino_upsell_service->getTriggerLabel($upsell)) . '</span>';
|
||||||
echo '<div class="mt-3 flex items-center gap-3">';
|
|
||||||
echo '<span class="rounded-full bg-green-50 px-3 py-1 text-sm font-medium text-green-700">' . esc_html($sodino_upsell_service->getDiscountLabel($upsell)) . '</span>';
|
|
||||||
echo '<span class="text-sm text-gray-600">' . esc_html($sodino_upsell_service->getTriggerLabel($upsell)) . '</span>';
|
|
||||||
echo '</div>';
|
|
||||||
echo '</div>';
|
|
||||||
echo '</div>';
|
|
||||||
echo '<div class="mt-5 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">';
|
|
||||||
echo '<div class="text-lg font-semibold text-gray-900">' . $priceHtml . '</div>';
|
|
||||||
echo '<a href="' . $addToCartUrl . '" class="inline-flex items-center justify-center rounded-full bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700">' . esc_html__('افزودن به سبد', 'sodino') . '</a>';
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
echo '<p class="sodino-upsell-title">' . esc_html($upsell->title) . '</p>';
|
||||||
|
echo '<h3>' . esc_html($product->get_name()) . '</h3>';
|
||||||
|
echo '<div class="sodino-upsell-footer">';
|
||||||
|
echo '<div class="sodino-upsell-price">' . $priceHtml;
|
||||||
|
if ($savings > 0) {
|
||||||
|
echo '<small>' . esc_html(sprintf(__('صرفهجویی: %s', 'sodino'), wp_strip_all_tags(wc_price($savings)))) . '</small>';
|
||||||
|
}
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
echo '<a href="' . esc_url($addToCartUrl) . '" class="sodino-upsell-button">' . esc_html__('افزودن با تخفیف', 'sodino') . '</a>';
|
||||||
|
echo '</div></div>';
|
||||||
|
echo '</article>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</section>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_get_upsell_add_to_cart_url($product, $upsell) {
|
||||||
|
return add_query_arg(
|
||||||
|
[
|
||||||
|
'add-to-cart' => $product->get_id(),
|
||||||
|
'quantity' => 1,
|
||||||
|
'sodino_upsell_id' => $upsell->id,
|
||||||
|
'sodino_upsell_nonce' => wp_create_nonce('sodino_apply_upsell_' . $upsell->id),
|
||||||
|
],
|
||||||
|
wc_get_cart_url()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_add_upsell_cart_item_data($cart_item_data, $product_id, $variation_id, $quantity) {
|
||||||
|
if (empty($_REQUEST['sodino_upsell_id'])) {
|
||||||
|
return $cart_item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$upsellId = absint(wp_unslash($_REQUEST['sodino_upsell_id']));
|
||||||
|
$nonce = sanitize_text_field(wp_unslash($_REQUEST['sodino_upsell_nonce'] ?? ''));
|
||||||
|
if (!$upsellId || !wp_verify_nonce($nonce, 'sodino_apply_upsell_' . $upsellId)) {
|
||||||
|
return $cart_item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $sodino_upsell_service;
|
||||||
|
if (!isset($sodino_upsell_service) || !WC()->cart) {
|
||||||
|
return $cart_item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$upsell = $sodino_upsell_service->getById($upsellId);
|
||||||
|
if (
|
||||||
|
!$sodino_upsell_service->canApplyToProduct($upsell, $product_id, $variation_id)
|
||||||
|
|| !$sodino_upsell_service->isValidForCart($upsell, WC()->cart)
|
||||||
|
) {
|
||||||
|
return $cart_item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product = wc_get_product($variation_id ?: $product_id);
|
||||||
|
if (!$product) {
|
||||||
|
return $cart_item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalPrice = floatval($product->get_price());
|
||||||
|
$discountedPrice = $sodino_upsell_service->calculateDiscountedPrice($originalPrice, $upsell);
|
||||||
|
|
||||||
|
$cart_item_data['sodino_upsell'] = [
|
||||||
|
'id' => (int) $upsell->id,
|
||||||
|
'title' => sanitize_text_field($upsell->title),
|
||||||
|
'discount_type' => sanitize_key($upsell->discount_type),
|
||||||
|
'discount_value' => floatval($upsell->discount_value),
|
||||||
|
'original_price' => $originalPrice,
|
||||||
|
'discounted_price' => $discountedPrice,
|
||||||
|
];
|
||||||
|
$cart_item_data['sodino_upsell_key'] = md5(wp_json_encode($cart_item_data['sodino_upsell']) . microtime(true));
|
||||||
|
|
||||||
|
return $cart_item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_restore_upsell_cart_item_data($cart_item, $values) {
|
||||||
|
if (!empty($values['sodino_upsell'])) {
|
||||||
|
$cart_item['sodino_upsell'] = $values['sodino_upsell'];
|
||||||
|
$cart_item['sodino_upsell_key'] = $values['sodino_upsell_key'] ?? '';
|
||||||
|
}
|
||||||
|
return $cart_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_apply_upsell_cart_prices($cart) {
|
||||||
|
if (is_admin() && !defined('DOING_AJAX')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$cart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($cart->get_cart() as $cartItem) {
|
||||||
|
if (empty($cartItem['sodino_upsell']['discounted_price']) || empty($cartItem['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cartItem['data']->set_price(max(0, floatval($cartItem['sodino_upsell']['discounted_price'])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_display_upsell_cart_item_data($item_data, $cart_item) {
|
||||||
|
if (empty($cart_item['sodino_upsell'])) {
|
||||||
|
return $item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$upsell = $cart_item['sodino_upsell'];
|
||||||
|
$item_data[] = [
|
||||||
|
'key' => __('پیشنهاد سودینو', 'sodino'),
|
||||||
|
'value' => esc_html($upsell['title']),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!empty($upsell['original_price']) && floatval($upsell['discounted_price']) < floatval($upsell['original_price'])) {
|
||||||
|
$item_data[] = [
|
||||||
|
'key' => __('تخفیف آپسل', 'sodino'),
|
||||||
|
'value' => wp_kses_post(wc_price(floatval($upsell['original_price']) - floatval($upsell['discounted_price']))),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_add_upsell_order_item_meta($item, $cart_item_key, $values, $order) {
|
||||||
|
if (empty($values['sodino_upsell'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$upsell = $values['sodino_upsell'];
|
||||||
|
$item->add_meta_data('_sodino_upsell_id', (int) $upsell['id'], true);
|
||||||
|
$item->add_meta_data(__('پیشنهاد سودینو', 'sodino'), sanitize_text_field($upsell['title']), true);
|
||||||
|
$item->add_meta_data(__('قیمت اصلی آپسل', 'sodino'), wc_price(floatval($upsell['original_price'])), true);
|
||||||
|
$item->add_meta_data(__('قیمت تخفیفی آپسل', 'sodino'), wc_price(floatval($upsell['discounted_price'])), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_track_upsell_conversion($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
|
||||||
|
if (empty($cart_item_data['sodino_upsell']['id'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $sodino_upsell_service;
|
||||||
|
if (isset($sodino_upsell_service)) {
|
||||||
|
$sodino_upsell_service->incrementConversion((int) $cart_item_data['sodino_upsell']['id']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user