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:
@@ -3,8 +3,10 @@ namespace Sodino\Controllers;
|
||||
|
||||
use Sodino\Repositories\RuleRepository;
|
||||
use Sodino\Repositories\UpsellRepository;
|
||||
use Sodino\Repositories\BannerRepository;
|
||||
use Sodino\Models\Rule;
|
||||
use Sodino\Models\Upsell;
|
||||
use Sodino\Models\Banner;
|
||||
|
||||
/**
|
||||
* Admin Controller
|
||||
@@ -12,10 +14,12 @@ use Sodino\Models\Upsell;
|
||||
class AdminController {
|
||||
private $ruleRepository;
|
||||
private $upsellRepository;
|
||||
private $bannerRepository;
|
||||
|
||||
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository) {
|
||||
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository, BannerRepository $bannerRepository) {
|
||||
$this->ruleRepository = $ruleRepository;
|
||||
$this->upsellRepository = $upsellRepository;
|
||||
$this->bannerRepository = $bannerRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +72,24 @@ class AdminController {
|
||||
[$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(
|
||||
'sodino-rules',
|
||||
__('قیمت رقبا (بهزودی)', '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
|
||||
*/
|
||||
@@ -206,6 +251,28 @@ class AdminController {
|
||||
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() {
|
||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$upsell = $this->upsellRepository->getById($id);
|
||||
@@ -245,6 +312,34 @@ class AdminController {
|
||||
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() {
|
||||
if (!isset($_GET['_wpnonce']) || !in_array($_GET['action'], ['delete_upsell', 'toggle_upsell_status'], true) || !wp_verify_nonce($_GET['_wpnonce'], $_GET['action'])) {
|
||||
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() {
|
||||
if (!check_ajax_referer('sodino_search_products', 'security', false)) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user