feat: Add banner management functionality
- Implemented a new Banner model to represent banner data. - Created a BannerRepository for database interactions related to banners. - Developed a BannerService to handle business logic for banners. - Added admin views for listing and adding banners. - Integrated banner hooks for frontend rendering and click tracking. - Created frontend styles and scripts for banner display and interaction. - Updated database migrations to include a new banners table. - Enhanced AdminController to manage banner actions and pages.
This commit is contained in:
@@ -5,13 +5,15 @@ if (!defined('ABSPATH')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use Sodino\Controllers\AdminController;
|
use Sodino\Controllers\AdminController;
|
||||||
|
use Sodino\Repositories\BannerRepository;
|
||||||
use Sodino\Repositories\RuleRepository;
|
use Sodino\Repositories\RuleRepository;
|
||||||
use Sodino\Repositories\UpsellRepository;
|
use Sodino\Repositories\UpsellRepository;
|
||||||
|
|
||||||
// Initialize admin
|
// Initialize admin
|
||||||
$ruleRepository = new RuleRepository();
|
$ruleRepository = new RuleRepository();
|
||||||
$upsellRepository = new UpsellRepository();
|
$upsellRepository = new UpsellRepository();
|
||||||
$adminController = new AdminController($ruleRepository, $upsellRepository);
|
$bannerRepository = new BannerRepository();
|
||||||
|
$adminController = new AdminController($ruleRepository, $upsellRepository, $bannerRepository);
|
||||||
|
|
||||||
// Add menu
|
// Add menu
|
||||||
add_action('admin_menu', [$adminController, 'addMenu']);
|
add_action('admin_menu', [$adminController, 'addMenu']);
|
||||||
@@ -41,13 +43,20 @@ add_action('admin_enqueue_scripts', function($hook) use ($adminController) {
|
|||||||
'nonce' => wp_create_nonce('sodino_search_products'),
|
'nonce' => wp_create_nonce('sodino_search_products'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strpos($hook, 'sodino_page_sodino-add-banner') !== false) {
|
||||||
|
wp_enqueue_media();
|
||||||
|
wp_enqueue_script('sodino-banner-admin', plugin_dir_url(__FILE__) . 'js/banner-admin.js', ['jquery'], SODINO_VERSION, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle delete for any Sodino admin page
|
// Handle delete for any Sodino admin page
|
||||||
if (isset($_GET['page']) && strpos($_GET['page'], 'sodino') === 0 && isset($_GET['action']) && $_GET['action'] === 'delete') {
|
if (isset($_GET['page']) && strpos($_GET['page'], 'sodino') === 0 && isset($_GET['action']) && $_GET['action'] === 'delete') {
|
||||||
add_action('admin_init', [$adminController, 'handleDelete']);
|
add_action('admin_init', [$adminController, 'handleDelete']);
|
||||||
}
|
}
|
||||||
|
if (isset($_GET['page']) && strpos($_GET['page'], 'sodino') === 0 && isset($_GET['action']) && in_array($_GET['action'], ['delete_banner', 'toggle_banner_status'], true)) {
|
||||||
|
add_action('admin_init', [$adminController, 'handleBannerActions']);
|
||||||
|
}
|
||||||
// Handle upsell actions
|
// Handle upsell actions
|
||||||
if (isset($_GET['page']) && strpos($_GET['page'], 'sodino') === 0 && isset($_GET['action']) && in_array($_GET['action'], ['delete_upsell', 'toggle_upsell_status'], true)) {
|
if (isset($_GET['page']) && strpos($_GET['page'], 'sodino') === 0 && isset($_GET['action']) && in_array($_GET['action'], ['delete_upsell', 'toggle_upsell_status'], true)) {
|
||||||
add_action('admin_init', [$adminController, 'handleUpsellActions']);
|
add_action('admin_init', [$adminController, 'handleUpsellActions']);
|
||||||
|
|||||||
142
admin/class-banner-list-table.php
Normal file
142
admin/class-banner-list-table.php
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('WP_List_Table')) {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sodino_Banner_List_Table extends WP_List_Table {
|
||||||
|
private $repository;
|
||||||
|
private $items_per_page = 20;
|
||||||
|
|
||||||
|
public function __construct($repository) {
|
||||||
|
parent::__construct([
|
||||||
|
'singular' => 'sodino_banner',
|
||||||
|
'plural' => 'sodino_banners',
|
||||||
|
'ajax' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_columns() {
|
||||||
|
return [
|
||||||
|
'cb' => '<input type="checkbox" />',
|
||||||
|
'title' => __('عنوان', 'sodino'),
|
||||||
|
'position' => __('محل نمایش', 'sodino'),
|
||||||
|
'display_type' => __('نوع نمایش', 'sodino'),
|
||||||
|
'schedule' => __('زمانبندی', 'sodino'),
|
||||||
|
'status' => __('وضعیت', 'sodino'),
|
||||||
|
'stats' => __('آمار', 'sodino'),
|
||||||
|
'actions' => __('عملیات', 'sodino'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function get_sortable_columns() {
|
||||||
|
return [
|
||||||
|
'priority' => ['priority', true],
|
||||||
|
'title' => ['title', true],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function column_cb($item) {
|
||||||
|
return sprintf('<input type="checkbox" name="banner_ids[]" value="%d" />', $item->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_bulk_actions() {
|
||||||
|
return [
|
||||||
|
'delete' => __('حذف گروهی', 'sodino'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_title($item) {
|
||||||
|
$edit_url = admin_url('admin.php?page=sodino-add-banner&action=edit&id=' . $item->id);
|
||||||
|
return sprintf('<strong><a href="%s">%s</a></strong>', esc_url($edit_url), esc_html($item->title));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_position($item) {
|
||||||
|
$map = [
|
||||||
|
'top' => __('بالای سایت', 'sodino'),
|
||||||
|
'middle' => __('وسط محتوا', 'sodino'),
|
||||||
|
'bottom' => __('پایین', 'sodino'),
|
||||||
|
'product_page' => __('صفحه محصول', 'sodino'),
|
||||||
|
'cart' => __('سبد خرید', 'sodino'),
|
||||||
|
];
|
||||||
|
return $map[$item->position] ?? __('نامشخص', 'sodino');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_display_type($item) {
|
||||||
|
$map = [
|
||||||
|
'inline' => __('درون صفحه', 'sodino'),
|
||||||
|
'popup' => __('پاپآپ', 'sodino'),
|
||||||
|
'floating_bar' => __('نوار شناور', 'sodino'),
|
||||||
|
];
|
||||||
|
return $map[$item->display_type] ?? __('نامشخص', 'sodino');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_schedule($item) {
|
||||||
|
if (!empty($item->start_time) || !empty($item->end_time)) {
|
||||||
|
$start = $item->start_time ? date_i18n('Y/m/d H:i', strtotime($item->start_time)) : __('بدون شروع', 'sodino');
|
||||||
|
$end = $item->end_time ? date_i18n('Y/m/d H:i', strtotime($item->end_time)) : __('بدون پایان', 'sodino');
|
||||||
|
return sprintf('%s<br><small class="description">%s</small>', esc_html($start), esc_html($end));
|
||||||
|
}
|
||||||
|
return __('بدون محدودیت زمانی', 'sodino');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_status($item) {
|
||||||
|
return $item->status ? __('فعال', 'sodino') : __('غیرفعال', 'sodino');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_stats($item) {
|
||||||
|
$ctr = $item->impressions > 0 ? round(($item->clicks / $item->impressions) * 100, 2) : 0;
|
||||||
|
return sprintf('<div class="sodino-stats-grid"><p>%s: <strong>%s</strong></p><p>%s: <strong>%s</strong></p><p>CTR: <strong>%s%%</strong></p></div>',
|
||||||
|
__('نمایش', 'sodino'), esc_html(number_format_i18n($item->impressions)),
|
||||||
|
__('کلیک', 'sodino'), esc_html(number_format_i18n($item->clicks)),
|
||||||
|
esc_html(number_format_i18n($ctr))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function column_actions($item) {
|
||||||
|
$edit_url = admin_url('admin.php?page=sodino-add-banner&action=edit&id=' . $item->id);
|
||||||
|
$toggle_url = wp_nonce_url(admin_url('admin.php?page=sodino-banners&action=toggle_banner_status&id=' . $item->id), 'toggle_banner_status');
|
||||||
|
$delete_url = wp_nonce_url(admin_url('admin.php?page=sodino-banners&action=delete_banner&id=' . $item->id), 'delete_banner');
|
||||||
|
$toggle_label = $item->status ? __('غیرفعال کردن', 'sodino') : __('فعال کردن', 'sodino');
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s">%s</a> | <a href="%s">%s</a> | <a href="%s" onclick="return confirm(\'%s\');">%s</a>',
|
||||||
|
esc_url($edit_url), esc_html__('ویرایش', 'sodino'),
|
||||||
|
esc_url($toggle_url), esc_html($toggle_label),
|
||||||
|
esc_url($delete_url), esc_js(__('آیا از حذف این بنر مطمئن هستید؟', 'sodino')),
|
||||||
|
esc_html__('حذف', 'sodino')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare_items() {
|
||||||
|
$this->_column_headers = [$this->get_columns(), [], $this->get_sortable_columns()];
|
||||||
|
$this->process_bulk_action();
|
||||||
|
|
||||||
|
$all_items = $this->repository->getAll();
|
||||||
|
$current_page = $this->get_pagenum();
|
||||||
|
$total_items = count($all_items);
|
||||||
|
$this->items = array_slice($all_items, ($current_page - 1) * $this->items_per_page, $this->items_per_page);
|
||||||
|
|
||||||
|
$this->set_pagination_args([
|
||||||
|
'total_items' => $total_items,
|
||||||
|
'per_page' => $this->items_per_page,
|
||||||
|
'total_pages' => ceil($total_items / $this->items_per_page),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process_bulk_action() {
|
||||||
|
if ('delete' === $this->current_action()) {
|
||||||
|
$banner_ids = isset($_POST['banner_ids']) ? array_map('intval', $_POST['banner_ids']) : [];
|
||||||
|
if (!empty($banner_ids) && check_admin_referer('bulk-' . $this->_args['plural'])) {
|
||||||
|
foreach ($banner_ids as $id) {
|
||||||
|
$this->repository->delete($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -294,3 +294,94 @@
|
|||||||
#sodino-app .sd-chart-card {
|
#sodino-app .sd-chart-card {
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sodino-app {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: radial-gradient(circle at top right, rgba(99, 102, 241, 0.14), transparent 22%),
|
||||||
|
radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 15%),
|
||||||
|
#f8fafc;
|
||||||
|
padding: 30px 16px 50px;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .bg-white.rounded-lg.shadow-sm.border,
|
||||||
|
#sodino-app .bg-white.rounded-2xl.border {
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
border-color: rgba(148, 163, 184, 0.70);
|
||||||
|
box-shadow: 0 18px 35px rgba(15, 23, 42, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .bg-white.rounded-lg.shadow-sm.border,
|
||||||
|
#sodino-app .bg-white.rounded-2xl.border,
|
||||||
|
#sodino-app .bg-gray-50,
|
||||||
|
#sodino-app .sd-sidebar,
|
||||||
|
#sodino-app .sd-card,
|
||||||
|
#sodino-app .sd-chart-card {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .bg-gray-50 {
|
||||||
|
background: #f7fbff !important;
|
||||||
|
border-color: rgba(148, 163, 184, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .shadow-sm {
|
||||||
|
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.09) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .sd-sidebar,
|
||||||
|
#sodino-app .sd-card,
|
||||||
|
#sodino-app .sd-chart-card {
|
||||||
|
border-color: rgba(148, 163, 184, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .rounded-lg,
|
||||||
|
#sodino-app .rounded-2xl,
|
||||||
|
#sodino-app .rounded-md {
|
||||||
|
border-radius: 1.3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .space-y-2 > a {
|
||||||
|
display: block;
|
||||||
|
padding: 0.95rem 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
transition: all 0.18s ease-in-out;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .space-y-2 > a:hover {
|
||||||
|
background: rgba(99, 102, 241, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .text-gray-500,
|
||||||
|
#sodino-app .text-gray-600,
|
||||||
|
#sodino-app .text-gray-700 {
|
||||||
|
color: #334155 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app h1,
|
||||||
|
#sodino-app h2,
|
||||||
|
#sodino-app h3,
|
||||||
|
#sodino-app h4 {
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .bg-white.rounded-lg.shadow-sm.border p,
|
||||||
|
#sodino-app .bg-white.rounded-2xl.border p {
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .bg-white.rounded-lg.shadow-sm.border .inline-flex,
|
||||||
|
#sodino-app .bg-white.rounded-2xl.border .inline-flex {
|
||||||
|
box-shadow: 0 10px 22px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app table thead th {
|
||||||
|
color: #334155 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sodino-app .wp-list-table .column-name,
|
||||||
|
#sodino-app .wp-list-table .column-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|||||||
75
admin/js/banner-admin.js
Normal file
75
admin/js/banner-admin.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
(function () {
|
||||||
|
const contentTypeRadios = document.querySelectorAll('.sodino-banner-content-type');
|
||||||
|
const contentGroups = document.querySelectorAll('.sodino-banner-content-group');
|
||||||
|
const mediaButton = document.getElementById('sodino-banner-image-upload');
|
||||||
|
const imageInput = document.getElementById('content_value_image');
|
||||||
|
const presetButtons = document.querySelectorAll('.sodino-banner-preset');
|
||||||
|
const startInput = document.getElementById('start_time');
|
||||||
|
const endInput = document.getElementById('end_time');
|
||||||
|
|
||||||
|
function toggleFields() {
|
||||||
|
const selectedType = document.querySelector('.sodino-banner-content-type:checked');
|
||||||
|
const type = selectedType ? selectedType.value : 'image';
|
||||||
|
contentGroups.forEach((group) => {
|
||||||
|
group.style.display = group.dataset.type === type ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentTypeRadios.length) {
|
||||||
|
contentTypeRadios.forEach((radio) => radio.addEventListener('change', toggleFields));
|
||||||
|
toggleFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaButton && imageInput && window.wp && wp.media) {
|
||||||
|
mediaButton.addEventListener('click', function () {
|
||||||
|
const mediaFrame = wp.media({
|
||||||
|
title: 'انتخاب تصویر بنر',
|
||||||
|
button: { text: 'انتخاب' },
|
||||||
|
multiple: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaFrame.on('select', function () {
|
||||||
|
const attachment = mediaFrame.state().get('selection').first().toJSON();
|
||||||
|
imageInput.value = attachment.url;
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaFrame.open();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDatetimeLocal = (element, date) => {
|
||||||
|
if (!element) return;
|
||||||
|
const tzOffset = date.getTimezoneOffset() * 60000;
|
||||||
|
const localISO = new Date(date - tzOffset).toISOString().slice(0, 16);
|
||||||
|
element.value = localISO;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (presetButtons.length) {
|
||||||
|
presetButtons.forEach((button) => {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const preset = this.dataset.preset;
|
||||||
|
const now = new Date();
|
||||||
|
if (preset === 'today') {
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0);
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59);
|
||||||
|
setDatetimeLocal(startInput, start);
|
||||||
|
setDatetimeLocal(endInput, end);
|
||||||
|
}
|
||||||
|
if (preset === 'weekend') {
|
||||||
|
const friday = new Date(now);
|
||||||
|
friday.setDate(friday.getDate() + ((5 - friday.getDay() + 7) % 7));
|
||||||
|
const saturday = new Date(friday);
|
||||||
|
saturday.setDate(friday.getDate() + 1);
|
||||||
|
setDatetimeLocal(startInput, new Date(friday.getFullYear(), friday.getMonth(), friday.getDate(), 0, 0));
|
||||||
|
setDatetimeLocal(endInput, new Date(saturday.getFullYear(), saturday.getMonth(), saturday.getDate(), 23, 59));
|
||||||
|
}
|
||||||
|
if (preset === 'hourly') {
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 18, 0);
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 0);
|
||||||
|
setDatetimeLocal(startInput, start);
|
||||||
|
setDatetimeLocal(endInput, end);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
219
admin/views/banner-form.php
Normal file
219
admin/views/banner-form.php
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<?php
|
||||||
|
// Prevent direct access
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-add-banner');
|
||||||
|
?>
|
||||||
|
<div id="sodino-app" class="min-h-screen bg-gray-50" dir="rtl">
|
||||||
|
<div class="bg-white border-b border-gray-200 shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900"><?php echo $banner->id ? __('ویرایش بنر', 'sodino') : __('افزودن بنر جدید', 'sodino'); ?></h1>
|
||||||
|
<p class="mt-1 text-sm text-gray-500"><?php _e('تنظیمات تبلیغات هوشمند و زمانبندی نمایش بنر در سایت.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
<a href="<?php echo admin_url('admin.php?page=sodino-banners'); ?>" class="inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-gray-700 border border-gray-200 shadow-sm hover:bg-gray-50">
|
||||||
|
<?php _e('بازگشت به لیست بنرها', 'sodino'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<div class="grid gap-8 lg:grid-cols-[280px_1fr]">
|
||||||
|
<aside class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
|
||||||
|
<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-xl 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-xl 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-banners'); ?>" class="block px-3 py-2 rounded-xl 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-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'); ?>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="space-y-6">
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field('sodino_save_banner', 'sodino_banner_nonce'); ?>
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900"><?php _e('اطلاعات اصلی', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-2 text-sm text-gray-500"><?php _e('عنوان، محتوا و لینک بنر را تعریف کنید.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="title"><?php _e('عنوان بنر', 'sodino'); ?></label>
|
||||||
|
<input type="text" name="title" id="title" value="<?php echo esc_attr($banner->title); ?>" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="block text-sm font-medium text-gray-700 mb-2"><?php _e('نوع محتوا', 'sodino'); ?></span>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<?php $content_types = ['image' => __('تصویر', 'sodino'), 'html' => __('HTML', 'sodino'), 'shortcode' => __('شورتکد', 'sodino')]; ?>
|
||||||
|
<?php foreach ($content_types as $value => $label) : ?>
|
||||||
|
<label class="flex items-center gap-3 rounded-2xl border border-gray-300 bg-white px-4 py-3 cursor-pointer hover:border-blue-300">
|
||||||
|
<input type="radio" name="content_type" value="<?php echo esc_attr($value); ?>" <?php checked($banner->content_type, $value); ?> class="h-4 w-4 text-blue-600 sodino-banner-content-type">
|
||||||
|
<span class="text-sm text-gray-700"><?php echo esc_html($label); ?></span>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6">
|
||||||
|
<div class="sodino-banner-content-group" data-type="image">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="content_value_image"><?php _e('آدرس تصویر بنر', 'sodino'); ?></label>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<input type="text" name="content_value" id="content_value_image" value="<?php echo esc_attr($banner->content_type === 'image' ? $banner->content_value : ''); ?>" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
<button type="button" id="sodino-banner-image-upload" class="inline-flex items-center rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50"><?php _e('انتخاب تصویر', 'sodino'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sodino-banner-content-group" data-type="html">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="content_value_html"><?php _e('محتوای HTML بنر', 'sodino'); ?></label>
|
||||||
|
<textarea name="content_value" id="content_value_html" rows="6" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100"><?php echo esc_textarea($banner->content_type === 'html' ? $banner->content_value : ''); ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sodino-banner-content-group" data-type="shortcode">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="content_value_shortcode"><?php _e('شورتکد بنر', 'sodino'); ?></label>
|
||||||
|
<textarea name="content_value" id="content_value_shortcode" rows="4" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100"><?php echo esc_textarea($banner->content_type === 'shortcode' ? $banner->content_value : ''); ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="link_url"><?php _e('لینک بنر (اختیاری)', 'sodino'); ?></label>
|
||||||
|
<input type="url" name="link_url" id="link_url" value="<?php echo esc_attr($banner->link_url); ?>" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100" placeholder="https://">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900"><?php _e('نمایش', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-2 text-sm text-gray-500"><?php _e('محل و سبک نمایش بنر را مشخص کنید.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="position"><?php _e('محل نمایش', 'sodino'); ?></label>
|
||||||
|
<select name="position" id="position" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
<option value="top" <?php selected($banner->position, 'top'); ?>><?php _e('بالای سایت', 'sodino'); ?></option>
|
||||||
|
<option value="middle" <?php selected($banner->position, 'middle'); ?>><?php _e('وسط محتوا', 'sodino'); ?></option>
|
||||||
|
<option value="bottom" <?php selected($banner->position, 'bottom'); ?>><?php _e('پایین', 'sodino'); ?></option>
|
||||||
|
<option value="product_page" <?php selected($banner->position, 'product_page'); ?>><?php _e('صفحه محصول', 'sodino'); ?></option>
|
||||||
|
<option value="cart" <?php selected($banner->position, 'cart'); ?>><?php _e('سبد خرید', 'sodino'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2"><?php _e('نوع نمایش', 'sodino'); ?></label>
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<?php $display_types = ['inline' => __('داخل صفحه', 'sodino'), 'popup' => __('پاپآپ', 'sodino'), 'floating_bar' => __('نوار شناور', 'sodino')]; ?>
|
||||||
|
<?php foreach ($display_types as $value => $label) : ?>
|
||||||
|
<label class="flex items-center gap-3 rounded-2xl border border-gray-300 bg-white px-4 py-3 cursor-pointer hover:border-blue-300">
|
||||||
|
<input type="radio" name="display_type" value="<?php echo esc_attr($value); ?>" <?php checked($banner->display_type, $value); ?> class="h-4 w-4 text-blue-600">
|
||||||
|
<span class="text-sm text-gray-700"><?php echo esc_html($label); ?></span>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900"><?php _e('زمانبندی', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-2 text-sm text-gray-500"><?php _e('زمان شروع و پایان نمایش بنر را تنظیم کنید.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
|
<button type="button" class="sodino-banner-preset inline-flex items-center justify-center rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm font-medium text-gray-700 shadow-sm hover:border-blue-300" data-preset="today"><?php _e('فقط امروز', 'sodino'); ?></button>
|
||||||
|
<button type="button" class="sodino-banner-preset inline-flex items-center justify-center rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm font-medium text-gray-700 shadow-sm hover:border-blue-300" data-preset="weekend"><?php _e('فقط آخر هفته', 'sodino'); ?></button>
|
||||||
|
<button type="button" class="sodino-banner-preset inline-flex items-center justify-center rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm font-medium text-gray-700 shadow-sm hover:border-blue-300" data-preset="hourly"><?php _e('ساعتی (18:00 تا 23:00)', 'sodino'); ?></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="start_time"><?php _e('تاریخ شروع', 'sodino'); ?></label>
|
||||||
|
<input type="datetime-local" name="start_time" id="start_time" value="<?php echo esc_attr($banner->start_time ? date('Y-m-d\TH:i', strtotime($banner->start_time)) : ''); ?>" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="end_time"><?php _e('تاریخ پایان', 'sodino'); ?></label>
|
||||||
|
<input type="datetime-local" name="end_time" id="end_time" value="<?php echo esc_attr($banner->end_time ? date('Y-m-d\TH:i', strtotime($banner->end_time)) : ''); ?>" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900"><?php _e('هدفگذاری', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-2 text-sm text-gray-500"><?php _e('نوع کاربر و دستگاه نمایش بنر را مشخص کنید.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="user_target"><?php _e('نوع کاربر', 'sodino'); ?></label>
|
||||||
|
<select name="user_target" id="user_target" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
<option value="all" <?php selected($banner->user_target, 'all'); ?>><?php _e('همه', 'sodino'); ?></option>
|
||||||
|
<option value="new" <?php selected($banner->user_target, 'new'); ?>><?php _e('کاربر جدید', 'sodino'); ?></option>
|
||||||
|
<option value="returning" <?php selected($banner->user_target, 'returning'); ?>><?php _e('بازگشتی', 'sodino'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="device_target"><?php _e('دستگاه', 'sodino'); ?></label>
|
||||||
|
<select name="device_target" id="device_target" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
<option value="all" <?php selected($banner->device_target, 'all'); ?>><?php _e('همه', 'sodino'); ?></option>
|
||||||
|
<option value="desktop" <?php selected($banner->device_target, 'desktop'); ?>><?php _e('دسکتاپ', 'sodino'); ?></option>
|
||||||
|
<option value="mobile" <?php selected($banner->device_target, 'mobile'); ?>><?php _e('موبایل', 'sodino'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900"><?php _e('تنظیمات پیشرفته', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-2 text-sm text-gray-500"><?php _e('اولویت و وضعیت نمایش بنر را تنظیم کنید.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-3">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="priority"><?php _e('اولویت', 'sodino'); ?></label>
|
||||||
|
<input type="number" name="priority" id="priority" value="<?php echo esc_attr($banner->priority); ?>" min="1" class="w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-gray-700 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-100">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="impressions"><?php _e('نمایشها', 'sodino'); ?></label>
|
||||||
|
<input type="number" id="impressions" value="<?php echo esc_attr($banner->impressions); ?>" disabled class="w-full rounded-2xl border border-gray-300 bg-gray-100 px-4 py-3 text-gray-700 shadow-sm">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2" for="clicks"><?php _e('کلیکها', 'sodino'); ?></label>
|
||||||
|
<input type="number" id="clicks" value="<?php echo esc_attr($banner->clicks); ?>" disabled class="w-full rounded-2xl border border-gray-300 bg-gray-100 px-4 py-3 text-gray-700 shadow-sm">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<label class="inline-flex items-center gap-3 rounded-2xl border border-gray-300 bg-white px-4 py-3 cursor-pointer hover:border-blue-300">
|
||||||
|
<input type="checkbox" name="status" value="1" <?php checked($banner->status, 1); ?> class="h-4 w-4 text-blue-600">
|
||||||
|
<span class="text-sm text-gray-700"><?php _e('فعال باشد', 'sodino'); ?></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm text-right">
|
||||||
|
<button type="submit" class="inline-flex items-center justify-center rounded-2xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 transition-colors">
|
||||||
|
<?php echo $banner->id ? __('بهروزرسانی بنر', 'sodino') : __('ذخیره بنر', 'sodino'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
66
admin/views/banner-list.php
Normal file
66
admin/views/banner-list.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
// Prevent direct access
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-banners');
|
||||||
|
?>
|
||||||
|
<div id="sodino-app" class="min-h-screen bg-gray-50" dir="rtl">
|
||||||
|
<div class="bg-white border-b border-gray-200 shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
|
<div class="flex items-center justify-between gap-4">
|
||||||
|
<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>
|
||||||
|
<a href="<?php echo admin_url('admin.php?page=sodino-add-banner'); ?>" class="inline-flex items-center gap-2 rounded-full bg-blue-600 px-5 py-3 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
|
||||||
|
<?php _e('افزودن بنر جدید', 'sodino'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<div class="grid gap-8 lg:grid-cols-[280px_1fr]">
|
||||||
|
<aside class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
|
||||||
|
<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-xl 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-xl 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-banners'); ?>" class="block px-3 py-2 rounded-xl 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-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'); ?>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="space-y-6">
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
|
||||||
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900"><?php _e('لیست بنرها', 'sodino'); ?></h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500"><?php _e('نمایش، ویرایش، فعال/غیرفعال و حذف گروهی بنرها.', 'sodino'); ?></p>
|
||||||
|
</div>
|
||||||
|
<span class="inline-flex items-center rounded-full bg-blue-50 px-4 py-2 text-sm font-medium text-blue-700">
|
||||||
|
<?php _e('حداکثر ۲ بنر همزمان نمایش داده میشوند', 'sodino'); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field('bulk-sodino_banners'); ?>
|
||||||
|
<?php $bannerTable->display(); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -3,8 +3,10 @@ namespace Sodino\Controllers;
|
|||||||
|
|
||||||
use Sodino\Repositories\RuleRepository;
|
use Sodino\Repositories\RuleRepository;
|
||||||
use Sodino\Repositories\UpsellRepository;
|
use Sodino\Repositories\UpsellRepository;
|
||||||
|
use Sodino\Repositories\BannerRepository;
|
||||||
use Sodino\Models\Rule;
|
use Sodino\Models\Rule;
|
||||||
use Sodino\Models\Upsell;
|
use Sodino\Models\Upsell;
|
||||||
|
use Sodino\Models\Banner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin Controller
|
* Admin Controller
|
||||||
@@ -12,10 +14,12 @@ use Sodino\Models\Upsell;
|
|||||||
class AdminController {
|
class AdminController {
|
||||||
private $ruleRepository;
|
private $ruleRepository;
|
||||||
private $upsellRepository;
|
private $upsellRepository;
|
||||||
|
private $bannerRepository;
|
||||||
|
|
||||||
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository) {
|
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository, BannerRepository $bannerRepository) {
|
||||||
$this->ruleRepository = $ruleRepository;
|
$this->ruleRepository = $ruleRepository;
|
||||||
$this->upsellRepository = $upsellRepository;
|
$this->upsellRepository = $upsellRepository;
|
||||||
|
$this->bannerRepository = $bannerRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,6 +72,24 @@ class AdminController {
|
|||||||
[$this, 'addUpsellPage']
|
[$this, 'addUpsellPage']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_submenu_page(
|
||||||
|
'sodino-rules',
|
||||||
|
__('بنرهای هوشمند', 'sodino'),
|
||||||
|
__('بنرهای هوشمند', 'sodino'),
|
||||||
|
'manage_options',
|
||||||
|
'sodino-banners',
|
||||||
|
[$this, 'bannersPage']
|
||||||
|
);
|
||||||
|
|
||||||
|
add_submenu_page(
|
||||||
|
'sodino-rules',
|
||||||
|
__('افزودن بنر', 'sodino'),
|
||||||
|
__('افزودن بنر', 'sodino'),
|
||||||
|
'manage_options',
|
||||||
|
'sodino-add-banner',
|
||||||
|
[$this, 'addBannerPage']
|
||||||
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'sodino-rules',
|
'sodino-rules',
|
||||||
__('قیمت رقبا (بهزودی)', 'sodino'),
|
__('قیمت رقبا (بهزودی)', 'sodino'),
|
||||||
@@ -192,6 +214,29 @@ class AdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Banners list page
|
||||||
|
*/
|
||||||
|
public function bannersPage() {
|
||||||
|
$this->listBannersPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or edit banner page
|
||||||
|
*/
|
||||||
|
public function addBannerPage() {
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'edit') {
|
||||||
|
return $this->editBannerPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$this->saveBanner();
|
||||||
|
} else {
|
||||||
|
$banner = new Banner();
|
||||||
|
include SODINO_PLUGIN_DIR . 'admin/views/banner-form.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Competitor price page
|
* Competitor price page
|
||||||
*/
|
*/
|
||||||
@@ -206,6 +251,28 @@ class AdminController {
|
|||||||
include SODINO_PLUGIN_DIR . 'admin/views/upsell-list.php';
|
include SODINO_PLUGIN_DIR . 'admin/views/upsell-list.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function listBannersPage() {
|
||||||
|
require_once SODINO_PLUGIN_DIR . 'admin/class-banner-list-table.php';
|
||||||
|
$bannerTable = new \Sodino_Banner_List_Table($this->bannerRepository);
|
||||||
|
$bannerTable->prepare_items();
|
||||||
|
include SODINO_PLUGIN_DIR . 'admin/views/banner-list.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function editBannerPage() {
|
||||||
|
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||||
|
$banner = $this->bannerRepository->getById($id);
|
||||||
|
|
||||||
|
if (!$banner) {
|
||||||
|
wp_die(__('بنر پیدا نشد', 'sodino'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$this->saveBanner($banner);
|
||||||
|
} else {
|
||||||
|
include SODINO_PLUGIN_DIR . 'admin/views/banner-form.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function editUpsellPage() {
|
private function editUpsellPage() {
|
||||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||||
$upsell = $this->upsellRepository->getById($id);
|
$upsell = $this->upsellRepository->getById($id);
|
||||||
@@ -245,6 +312,34 @@ class AdminController {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function saveBanner($banner = null) {
|
||||||
|
if (!isset($_POST['sodino_banner_nonce']) || !wp_verify_nonce($_POST['sodino_banner_nonce'], 'sodino_save_banner')) {
|
||||||
|
wp_die(__('خطای امنیتی رخ داد.', 'sodino'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$banner) {
|
||||||
|
$banner = new Banner();
|
||||||
|
}
|
||||||
|
|
||||||
|
$banner->title = sanitize_text_field($_POST['title'] ?? '');
|
||||||
|
$banner->content_type = sanitize_text_field($_POST['content_type'] ?? 'image');
|
||||||
|
$banner->content_value = isset($_POST['content_value']) ? wp_kses_post($_POST['content_value']) : '';
|
||||||
|
$banner->link_url = esc_url_raw($_POST['link_url'] ?? '');
|
||||||
|
$banner->position = sanitize_text_field($_POST['position'] ?? 'top');
|
||||||
|
$banner->display_type = sanitize_text_field($_POST['display_type'] ?? 'inline');
|
||||||
|
$banner->start_time = sanitize_text_field($_POST['start_time'] ?? '');
|
||||||
|
$banner->end_time = sanitize_text_field($_POST['end_time'] ?? '');
|
||||||
|
$banner->user_target = sanitize_text_field($_POST['user_target'] ?? 'all');
|
||||||
|
$banner->device_target = sanitize_text_field($_POST['device_target'] ?? 'all');
|
||||||
|
$banner->priority = max(1, intval($_POST['priority'] ?? 10));
|
||||||
|
$banner->status = isset($_POST['status']) ? 1 : 0;
|
||||||
|
|
||||||
|
$this->bannerRepository->save($banner);
|
||||||
|
|
||||||
|
wp_safe_redirect(admin_url('admin.php?page=sodino-banners'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
public function handleUpsellActions() {
|
public function handleUpsellActions() {
|
||||||
if (!isset($_GET['_wpnonce']) || !in_array($_GET['action'], ['delete_upsell', 'toggle_upsell_status'], true) || !wp_verify_nonce($_GET['_wpnonce'], $_GET['action'])) {
|
if (!isset($_GET['_wpnonce']) || !in_array($_GET['action'], ['delete_upsell', 'toggle_upsell_status'], true) || !wp_verify_nonce($_GET['_wpnonce'], $_GET['action'])) {
|
||||||
return;
|
return;
|
||||||
@@ -272,6 +367,33 @@ class AdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function handleBannerActions() {
|
||||||
|
if (!isset($_GET['_wpnonce']) || !in_array($_GET['action'], ['delete_banner', 'toggle_banner_status'], true) || !wp_verify_nonce($_GET['_wpnonce'], $_GET['action'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||||
|
if (!$id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_GET['action'] === 'delete_banner') {
|
||||||
|
$this->bannerRepository->delete($id);
|
||||||
|
wp_safe_redirect(admin_url('admin.php?page=sodino-banners'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_GET['action'] === 'toggle_banner_status') {
|
||||||
|
$banner = $this->bannerRepository->getById($id);
|
||||||
|
if ($banner) {
|
||||||
|
$banner->status = $banner->status ? 0 : 1;
|
||||||
|
$this->bannerRepository->save($banner);
|
||||||
|
}
|
||||||
|
wp_safe_redirect(admin_url('admin.php?page=sodino-banners'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function searchProductsAjax() {
|
public function searchProductsAjax() {
|
||||||
if (!check_ajax_referer('sodino_search_products', 'security', false)) {
|
if (!check_ajax_referer('sodino_search_products', 'security', false)) {
|
||||||
wp_send_json([]);
|
wp_send_json([]);
|
||||||
|
|||||||
64
app/Models/Banner.php
Normal file
64
app/Models/Banner.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
namespace Sodino\Models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Banner Model
|
||||||
|
*/
|
||||||
|
class Banner {
|
||||||
|
public $id;
|
||||||
|
public $title;
|
||||||
|
public $content_type;
|
||||||
|
public $content_value;
|
||||||
|
public $link_url;
|
||||||
|
public $position;
|
||||||
|
public $display_type;
|
||||||
|
public $start_time;
|
||||||
|
public $end_time;
|
||||||
|
public $user_target;
|
||||||
|
public $device_target;
|
||||||
|
public $priority;
|
||||||
|
public $status;
|
||||||
|
public $impressions;
|
||||||
|
public $clicks;
|
||||||
|
public $created_at;
|
||||||
|
|
||||||
|
public function __construct($data = []) {
|
||||||
|
$this->id = $data['id'] ?? null;
|
||||||
|
$this->title = $data['title'] ?? '';
|
||||||
|
$this->content_type = $data['content_type'] ?? 'image';
|
||||||
|
$this->content_value = $data['content_value'] ?? '';
|
||||||
|
$this->link_url = $data['link_url'] ?? '';
|
||||||
|
$this->position = $data['position'] ?? 'top';
|
||||||
|
$this->display_type = $data['display_type'] ?? 'inline';
|
||||||
|
$this->start_time = $data['start_time'] ?? null;
|
||||||
|
$this->end_time = $data['end_time'] ?? null;
|
||||||
|
$this->user_target = $data['user_target'] ?? 'all';
|
||||||
|
$this->device_target = $data['device_target'] ?? 'all';
|
||||||
|
$this->priority = isset($data['priority']) ? (int) $data['priority'] : 10;
|
||||||
|
$this->status = isset($data['status']) ? (int) $data['status'] : 1;
|
||||||
|
$this->impressions = isset($data['impressions']) ? (int) $data['impressions'] : 0;
|
||||||
|
$this->clicks = isset($data['clicks']) ? (int) $data['clicks'] : 0;
|
||||||
|
$this->created_at = $data['created_at'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'content_type' => $this->content_type,
|
||||||
|
'content_value' => $this->content_value,
|
||||||
|
'link_url' => $this->link_url,
|
||||||
|
'position' => $this->position,
|
||||||
|
'display_type' => $this->display_type,
|
||||||
|
'start_time' => $this->start_time,
|
||||||
|
'end_time' => $this->end_time,
|
||||||
|
'user_target' => $this->user_target,
|
||||||
|
'device_target' => $this->device_target,
|
||||||
|
'priority' => $this->priority,
|
||||||
|
'status' => $this->status,
|
||||||
|
'impressions' => $this->impressions,
|
||||||
|
'clicks' => $this->clicks,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/Repositories/BannerRepository.php
Normal file
81
app/Repositories/BannerRepository.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
namespace Sodino\Repositories;
|
||||||
|
|
||||||
|
use Sodino\Models\Banner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Banner Repository
|
||||||
|
*/
|
||||||
|
class BannerRepository {
|
||||||
|
private $table_name;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
global $wpdb;
|
||||||
|
$this->table_name = $wpdb->prefix . 'sodino_banners';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll() {
|
||||||
|
global $wpdb;
|
||||||
|
$results = $wpdb->get_results("SELECT * FROM {$this->table_name} ORDER BY priority DESC, id ASC", ARRAY_A);
|
||||||
|
$banners = [];
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$banners[] = new Banner($result);
|
||||||
|
}
|
||||||
|
return $banners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById($id) {
|
||||||
|
global $wpdb;
|
||||||
|
$result = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", $id), ARRAY_A);
|
||||||
|
return $result ? new Banner($result) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnabled() {
|
||||||
|
global $wpdb;
|
||||||
|
$results = $wpdb->get_results("SELECT * FROM {$this->table_name} WHERE status = 1 ORDER BY priority DESC, id ASC", ARRAY_A);
|
||||||
|
$banners = [];
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$banners[] = new Banner($result);
|
||||||
|
}
|
||||||
|
return $banners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(Banner $banner) {
|
||||||
|
global $wpdb;
|
||||||
|
$data = $banner->toArray();
|
||||||
|
unset($data['id'], $data['created_at']);
|
||||||
|
|
||||||
|
if ($banner->id) {
|
||||||
|
$wpdb->update($this->table_name, $data, ['id' => $banner->id]);
|
||||||
|
$this->clearCache();
|
||||||
|
return $banner->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wpdb->insert($this->table_name, $data);
|
||||||
|
$this->clearCache();
|
||||||
|
return $wpdb->insert_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id) {
|
||||||
|
global $wpdb;
|
||||||
|
$result = $wpdb->delete($this->table_name, ['id' => $id]);
|
||||||
|
$this->clearCache();
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incrementImpression($id) {
|
||||||
|
global $wpdb;
|
||||||
|
$wpdb->query($wpdb->prepare("UPDATE {$this->table_name} SET impressions = impressions + 1 WHERE id = %d", $id));
|
||||||
|
$this->clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incrementClick($id) {
|
||||||
|
global $wpdb;
|
||||||
|
$wpdb->query($wpdb->prepare("UPDATE {$this->table_name} SET clicks = clicks + 1 WHERE id = %d", $id));
|
||||||
|
$this->clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearCache() {
|
||||||
|
wp_cache_flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
111
app/Services/BannerService.php
Normal file
111
app/Services/BannerService.php
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
namespace Sodino\Services;
|
||||||
|
|
||||||
|
use Sodino\Models\Banner;
|
||||||
|
use Sodino\Repositories\BannerRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Banner Service
|
||||||
|
*/
|
||||||
|
class BannerService {
|
||||||
|
private $repository;
|
||||||
|
|
||||||
|
public function __construct(BannerRepository $repository) {
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveBanners(array $context = []) {
|
||||||
|
$position = $context['position'] ?? '';
|
||||||
|
$cache_key = $this->getCacheKey($position, $context);
|
||||||
|
$banners = wp_cache_get($cache_key, 'sodino_banners');
|
||||||
|
|
||||||
|
if ($banners === false) {
|
||||||
|
$banners = array_filter($this->repository->getEnabled(), function (Banner $banner) use ($context) {
|
||||||
|
return $this->filterByPosition($banner, $context)
|
||||||
|
&& $this->filterByTime($banner)
|
||||||
|
&& $this->filterByUserType($banner)
|
||||||
|
&& $this->filterByDevice($banner);
|
||||||
|
});
|
||||||
|
|
||||||
|
usort($banners, function (Banner $a, Banner $b) {
|
||||||
|
return $b->priority <=> $a->priority;
|
||||||
|
});
|
||||||
|
|
||||||
|
wp_cache_set($cache_key, $banners, 'sodino_banners', MINUTE_IN_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit = isset($context['limit']) ? (int) $context['limit'] : 1;
|
||||||
|
return $limit > 0 ? array_slice($banners, 0, $limit) : $banners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterByTime(Banner $banner) {
|
||||||
|
$now = current_time('timestamp');
|
||||||
|
|
||||||
|
if (!empty($banner->start_time)) {
|
||||||
|
$start = strtotime($banner->start_time);
|
||||||
|
if ($start !== false && $now < $start) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($banner->end_time)) {
|
||||||
|
$end = strtotime($banner->end_time);
|
||||||
|
if ($end !== false && $now > $end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterByUserType(Banner $banner) {
|
||||||
|
if ($banner->user_target === 'all') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userType = is_user_logged_in() ? 'returning' : 'new';
|
||||||
|
|
||||||
|
return $banner->user_target === $userType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterByDevice(Banner $banner) {
|
||||||
|
if ($banner->device_target === 'all') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$device = wp_is_mobile() ? 'mobile' : 'desktop';
|
||||||
|
return $banner->device_target === $device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterByPosition(Banner $banner, array $context) {
|
||||||
|
if (empty($context['position'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($banner->position !== $context['position']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($banner->position === 'product_page' && !is_singular('product')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($banner->position === 'cart' && !is_cart()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increaseImpression($bannerId) {
|
||||||
|
$this->repository->incrementImpression($bannerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increaseClick($bannerId) {
|
||||||
|
$this->repository->incrementClick($bannerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCacheKey($position, array $context) {
|
||||||
|
return 'sodino_active_banners_' . md5($position . '|' . serialize($context));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,11 +69,34 @@ function sodino_create_tables() {
|
|||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) $charset_collate;";
|
) $charset_collate;";
|
||||||
|
|
||||||
|
// Banner table
|
||||||
|
$banner_table = $wpdb->prefix . 'sodino_banners';
|
||||||
|
$banner_sql = "CREATE TABLE $banner_table (
|
||||||
|
id mediumint(9) NOT NULL AUTO_INCREMENT,
|
||||||
|
title varchar(255) NOT NULL,
|
||||||
|
content_type varchar(50) NOT NULL DEFAULT 'image',
|
||||||
|
content_value longtext NOT NULL,
|
||||||
|
link_url varchar(255) DEFAULT NULL,
|
||||||
|
position varchar(50) NOT NULL DEFAULT 'top',
|
||||||
|
display_type varchar(50) NOT NULL DEFAULT 'inline',
|
||||||
|
start_time datetime DEFAULT NULL,
|
||||||
|
end_time datetime DEFAULT NULL,
|
||||||
|
user_target varchar(50) NOT NULL DEFAULT 'all',
|
||||||
|
device_target varchar(50) NOT NULL DEFAULT 'all',
|
||||||
|
priority int(11) NOT NULL DEFAULT 10,
|
||||||
|
status tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
impressions bigint(20) NOT NULL DEFAULT 0,
|
||||||
|
clicks bigint(20) NOT NULL DEFAULT 0,
|
||||||
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) $charset_collate;";
|
||||||
|
|
||||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||||
dbDelta($rules_sql);
|
dbDelta($rules_sql);
|
||||||
dbDelta($events_sql);
|
dbDelta($events_sql);
|
||||||
dbDelta($upsell_sql);
|
dbDelta($upsell_sql);
|
||||||
|
dbDelta($banner_sql);
|
||||||
|
|
||||||
// Add version option
|
// Add version option
|
||||||
add_option('sodino_db_version', '1.2');
|
update_option('sodino_db_version', '1.3');
|
||||||
}
|
}
|
||||||
112
public/css/banner-frontend.css
Normal file
112
public/css/banner-frontend.css
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
.sodino-banner {
|
||||||
|
direction: rtl;
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999;
|
||||||
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-wrap {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 18px 22px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||||
|
border-radius: 1rem;
|
||||||
|
box-shadow: 0 18px 35px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-top {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 1100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 1100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-floating_bar {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 1100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-position-top.sodino-banner-floating_bar,
|
||||||
|
.sodino-banner-position-middle.sodino-banner-floating_bar {
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-position-bottom.sodino-banner-floating_bar,
|
||||||
|
.sodino-banner-position-cart.sodino-banner-floating_bar,
|
||||||
|
.sodino-banner-position-product_page.sodino-banner-floating_bar {
|
||||||
|
bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
max-width: 95%;
|
||||||
|
width: 680px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 30px 60px rgba(15, 23, 42, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
right: 16px;
|
||||||
|
background: rgba(15, 23, 42, 0.06);
|
||||||
|
border: none;
|
||||||
|
color: #0f172a;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-link {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sodino-banner-top,
|
||||||
|
.sodino-banner-bottom,
|
||||||
|
.sodino-banner-floating_bar,
|
||||||
|
.sodino-banner-popup {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sodino-banner-popup {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
165
public/hooks/banner-hooks.php
Normal file
165
public/hooks/banner-hooks.php
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
// Prevent direct access
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
use Sodino\Repositories\BannerRepository;
|
||||||
|
use Sodino\Services\BannerService;
|
||||||
|
|
||||||
|
global $sodino_banner_service;
|
||||||
|
$bannerRepository = new BannerRepository();
|
||||||
|
$sodino_banner_service = new BannerService($bannerRepository);
|
||||||
|
|
||||||
|
add_action('wp_head', 'sodino_render_top_banner', 1);
|
||||||
|
add_filter('the_content', 'sodino_render_middle_banner');
|
||||||
|
add_action('wp_footer', 'sodino_render_bottom_banner', 20);
|
||||||
|
add_action('woocommerce_after_single_product_summary', 'sodino_render_product_banner', 5);
|
||||||
|
add_action('woocommerce_before_cart', 'sodino_render_cart_banner');
|
||||||
|
add_action('wp_enqueue_scripts', 'sodino_enqueue_banner_assets');
|
||||||
|
add_action('wp_ajax_nopriv_sodino_banner_click', 'sodino_handle_banner_click');
|
||||||
|
add_action('wp_ajax_sodino_banner_click', 'sodino_handle_banner_click');
|
||||||
|
|
||||||
|
function sodino_enqueue_banner_assets() {
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_register_style('sodino-banner-frontend', plugin_dir_url(__FILE__) . '../css/banner-frontend.css', [], SODINO_VERSION);
|
||||||
|
wp_enqueue_style('sodino-banner-frontend');
|
||||||
|
wp_register_script('sodino-banner-frontend', plugin_dir_url(__FILE__) . '../js/banner-frontend.js', [], SODINO_VERSION, true);
|
||||||
|
wp_localize_script('sodino-banner-frontend', 'sodinoBannerFrontend', [
|
||||||
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('sodino_banner_click'),
|
||||||
|
]);
|
||||||
|
wp_enqueue_script('sodino-banner-frontend');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_get_banner_html($banner) {
|
||||||
|
global $sodino_banner_service;
|
||||||
|
$html = '';
|
||||||
|
$content = '';
|
||||||
|
|
||||||
|
switch ($banner->content_type) {
|
||||||
|
case 'image':
|
||||||
|
$image = esc_url($banner->content_value);
|
||||||
|
if (!empty($banner->link_url)) {
|
||||||
|
$content = sprintf('<a href="%s" class="sodino-banner-link" data-banner-id="%d">%s</a>', esc_url($banner->link_url), esc_attr($banner->id), '<img src="' . $image . '" alt="' . esc_attr($banner->title) . '" class="sodino-banner-image" />');
|
||||||
|
} else {
|
||||||
|
$content = '<img src="' . $image . '" alt="' . esc_attr($banner->title) . '" class="sodino-banner-image" />';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'shortcode':
|
||||||
|
$content = do_shortcode(wp_kses_post($banner->content_value));
|
||||||
|
break;
|
||||||
|
case 'html':
|
||||||
|
default:
|
||||||
|
$content = wp_kses_post($banner->content_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$linkAttributes = '';
|
||||||
|
if (!empty($banner->link_url) && $banner->content_type !== 'image') {
|
||||||
|
$linkAttributes = sprintf(' data-banner-id="%d" href="%s" class="sodino-banner-link"', esc_attr($banner->id), esc_url($banner->link_url));
|
||||||
|
}
|
||||||
|
|
||||||
|
$closeButton = '<button type="button" class="sodino-banner-close" aria-label="'.esc_attr__('بستن بنر', 'sodino').'">×</button>';
|
||||||
|
$wrapperClass = 'sodino-banner-wrap sodino-banner-' . esc_attr($banner->display_type) . ' sodino-banner-position-' . esc_attr($banner->position);
|
||||||
|
|
||||||
|
$style = $banner->display_type === 'popup' ? 'style="display:none;"' : '';
|
||||||
|
$html .= '<div class="' . $wrapperClass . '" data-banner-id="' . esc_attr($banner->id) . '" ' . $style . '>';
|
||||||
|
if ($banner->display_type === 'popup' || $banner->display_type === 'floating_bar') {
|
||||||
|
$html .= $closeButton;
|
||||||
|
}
|
||||||
|
$html .= '<div class="sodino-banner-content">';
|
||||||
|
if ($banner->content_type !== 'image' && !empty($banner->link_url)) {
|
||||||
|
$html .= '<a' . $linkAttributes . '>' . $content . '</a>';
|
||||||
|
} else {
|
||||||
|
$html .= $content;
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_render_banner_position($position) {
|
||||||
|
global $sodino_banner_service;
|
||||||
|
|
||||||
|
if (!isset($sodino_banner_service)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$banners = $sodino_banner_service->getActiveBanners(['position' => $position, 'limit' => 1]);
|
||||||
|
if (empty($banners)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$banner = reset($banners);
|
||||||
|
$sodino_banner_service->increaseImpression($banner->id);
|
||||||
|
return sodino_get_banner_html($banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_render_top_banner() {
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo sodino_render_banner_position('top');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_render_middle_banner($content) {
|
||||||
|
if (is_admin() || !is_singular() || !in_the_loop() || is_feed()) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$banner = sodino_render_banner_position('middle');
|
||||||
|
if (empty($banner)) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $banner . $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_render_bottom_banner() {
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo sodino_render_banner_position('bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_render_product_banner() {
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo sodino_render_banner_position('product_page');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_render_cart_banner() {
|
||||||
|
if (is_admin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo sodino_render_banner_position('cart');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sodino_handle_banner_click() {
|
||||||
|
if (!isset($_POST['banner_id']) || !isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'sodino_banner_click')) {
|
||||||
|
wp_send_json_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bannerId = intval($_POST['banner_id']);
|
||||||
|
if (!$bannerId) {
|
||||||
|
wp_send_json_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
global $sodino_banner_service;
|
||||||
|
if (!isset($sodino_banner_service)) {
|
||||||
|
wp_send_json_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sodino_banner_service->increaseClick($bannerId);
|
||||||
|
wp_send_json_success();
|
||||||
|
}
|
||||||
65
public/js/banner-frontend.js
Normal file
65
public/js/banner-frontend.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
(function () {
|
||||||
|
const closeButtons = document.querySelectorAll('.sodino-banner-close');
|
||||||
|
const clicked = new Set();
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
const value = '; ' + document.cookie;
|
||||||
|
const parts = value.split('; ' + name + '=');
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return parts.pop().split(';').shift();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendClick(bannerId) {
|
||||||
|
if (!bannerId || clicked.has(bannerId) || !window.sodinoBannerFrontend) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clicked.add(bannerId);
|
||||||
|
|
||||||
|
fetch(window.sodinoBannerFrontend.ajaxUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'action=sodino_banner_click&banner_id=' + encodeURIComponent(bannerId) + '&security=' + encodeURIComponent(window.sodinoBannerFrontend.nonce),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
const target = event.target.closest('.sodino-banner-link');
|
||||||
|
if (target) {
|
||||||
|
const bannerId = target.dataset.bannerId;
|
||||||
|
sendClick(bannerId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButtons.forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const wrapper = this.closest('.sodino-banner-wrap');
|
||||||
|
if (!wrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapper.style.display = 'none';
|
||||||
|
const bannerId = wrapper.dataset.bannerId;
|
||||||
|
if (bannerId) {
|
||||||
|
document.cookie = 'sodino_banner_' + bannerId + '=hidden; path=/; max-age=' + 60 * 60 * 24;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.sodino-banner-wrap').forEach(function (banner) {
|
||||||
|
const bannerId = banner.dataset.bannerId;
|
||||||
|
if (getCookie('sodino_banner_' + bannerId) === 'hidden') {
|
||||||
|
banner.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (banner.classList.contains('sodino-banner-popup')) {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (getCookie('sodino_banner_' + bannerId) !== 'hidden') {
|
||||||
|
banner.style.display = 'block';
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -89,6 +89,7 @@ function sodino_init() {
|
|||||||
require_once SODINO_PLUGIN_DIR . 'public/hooks/pricing-hooks.php';
|
require_once SODINO_PLUGIN_DIR . 'public/hooks/pricing-hooks.php';
|
||||||
require_once SODINO_PLUGIN_DIR . 'public/hooks/analytics-hooks.php';
|
require_once SODINO_PLUGIN_DIR . 'public/hooks/analytics-hooks.php';
|
||||||
require_once SODINO_PLUGIN_DIR . 'public/hooks/upsell-hooks.php';
|
require_once SODINO_PLUGIN_DIR . 'public/hooks/upsell-hooks.php';
|
||||||
|
require_once SODINO_PLUGIN_DIR . 'public/hooks/banner-hooks.php';
|
||||||
|
|
||||||
// Schedule analytics aggregation if needed
|
// Schedule analytics aggregation if needed
|
||||||
sodino_schedule_analytics();
|
sodino_schedule_analytics();
|
||||||
|
|||||||
Reference in New Issue
Block a user