Init(Core): create and add project
This commit is contained in:
158
app/Controllers/AdminController.php
Normal file
158
app/Controllers/AdminController.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
namespace Sodino\Controllers;
|
||||
|
||||
use Sodino\Repositories\RuleRepository;
|
||||
use Sodino\Models\Rule;
|
||||
|
||||
/**
|
||||
* Admin Controller
|
||||
*/
|
||||
class AdminController {
|
||||
private $ruleRepository;
|
||||
|
||||
public function __construct(RuleRepository $ruleRepository) {
|
||||
$this->ruleRepository = $ruleRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle admin menu
|
||||
*/
|
||||
public function addMenu() {
|
||||
add_menu_page(
|
||||
__('قیمتیار', 'sodino'),
|
||||
__('قیمتیار', 'sodino'),
|
||||
'manage_options',
|
||||
'sodino-rules',
|
||||
[$this, 'rulesPage'],
|
||||
'dashicons-money-alt',
|
||||
56
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
'sodino-rules',
|
||||
__('قوانین قیمتگذاری', 'sodino'),
|
||||
__('قوانین قیمتگذاری', 'sودino'),
|
||||
'manage_options',
|
||||
'sodino-rules',
|
||||
[$this, 'rulesPage']
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
'sodino-rules',
|
||||
__('افزودن قانون', 'sودino'),
|
||||
__('افزودن قانون', 'sودino'),
|
||||
'manage_options',
|
||||
'sodino-add-rule',
|
||||
[$this, 'addRulePage']
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
'sodino-rules',
|
||||
__('تنظیمات', 'sodino'),
|
||||
__('تنظیمات', 'sودینو'),
|
||||
'manage_options',
|
||||
'sodino-settings',
|
||||
[$this, 'settingsPage']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules admin page
|
||||
*/
|
||||
public function rulesPage() {
|
||||
$this->listRulesPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* List rules page
|
||||
*/
|
||||
private function listRulesPage() {
|
||||
require_once SODINO_PLUGIN_DIR . 'admin/class-rules-list-table.php';
|
||||
|
||||
$rulesTable = new \Sodino_Rules_List_Table($this->ruleRepository);
|
||||
$rulesTable->prepare_items();
|
||||
|
||||
include SODINO_PLUGIN_DIR . 'admin/views/rules-list.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or edit rule page
|
||||
*/
|
||||
public function addRulePage() {
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'edit') {
|
||||
return $this->editRulePage();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$this->saveRule();
|
||||
} else {
|
||||
$rule = new Rule();
|
||||
include SODINO_PLUGIN_DIR . 'admin/views/rule-form.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings page
|
||||
*/
|
||||
public function settingsPage() {
|
||||
include SODINO_PLUGIN_DIR . 'admin/views/settings.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit rule page
|
||||
*/
|
||||
private function editRulePage() {
|
||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$rule = $this->ruleRepository->getById($id);
|
||||
|
||||
if (!$rule) {
|
||||
wp_die(__('Rule not found', 'sodino'));
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$this->saveRule($rule);
|
||||
} else {
|
||||
include SODINO_PLUGIN_DIR . 'admin/views/rule-form.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save rule
|
||||
*/
|
||||
private function saveRule($rule = null) {
|
||||
if (!isset($_POST['gheymatyar_rule_nonce']) || !wp_verify_nonce($_POST['gheymatyar_rule_nonce'], 'gheymatyar_save_rule')) {
|
||||
wp_die(__('خطای امنیتی رخ داد.', 'sodino'));
|
||||
}
|
||||
|
||||
if (!$rule) {
|
||||
$rule = new Rule();
|
||||
}
|
||||
|
||||
$rule->name = sanitize_text_field($_POST['name'] ?? '');
|
||||
$rule->condition_type = sanitize_text_field($_POST['condition_type'] ?? 'user_type');
|
||||
$rule->condition_value = sanitize_text_field($_POST['condition_value'] ?? 'new');
|
||||
$rule->action_type = sanitize_text_field($_POST['action_type'] ?? 'discount_percent');
|
||||
$rule->action_value = sanitize_text_field($_POST['action_value'] ?? '0');
|
||||
$rule->enabled = isset($_POST['enabled']) ? 1 : 0;
|
||||
|
||||
$this->ruleRepository->save($rule);
|
||||
|
||||
wp_safe_redirect(admin_url('admin.php?page=sodino-rules'));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete action
|
||||
*/
|
||||
public function handleDelete() {
|
||||
if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'delete_rule')) {
|
||||
wp_die(__('خطای امنیتی رخ داد.', 'sodino'));
|
||||
}
|
||||
|
||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$this->ruleRepository->delete($id);
|
||||
|
||||
wp_redirect(admin_url('admin.php?page=sodino-rules'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
85
app/Models/Rule.php
Normal file
85
app/Models/Rule.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace Sodino\Models;
|
||||
|
||||
/**
|
||||
* Rule Model
|
||||
*/
|
||||
class Rule {
|
||||
public $id;
|
||||
public $name;
|
||||
public $conditions;
|
||||
public $actions;
|
||||
public $priority;
|
||||
public $start_date;
|
||||
public $end_date;
|
||||
public $enabled;
|
||||
public $condition_type;
|
||||
public $condition_value;
|
||||
public $action_type;
|
||||
public $action_value;
|
||||
public $created_at;
|
||||
public $updated_at;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($data = []) {
|
||||
$this->id = $data['id'] ?? null;
|
||||
$this->name = $data['name'] ?? '';
|
||||
$this->conditions = $this->parseJsonField($data['conditions'] ?? '[]');
|
||||
$this->actions = $this->parseJsonField($data['actions'] ?? '[]');
|
||||
$this->priority = isset($data['priority']) ? (int) $data['priority'] : 10;
|
||||
$this->start_date = $data['start_date'] ?? null;
|
||||
$this->end_date = $data['end_date'] ?? null;
|
||||
$this->enabled = isset($data['enabled']) ? (int) $data['enabled'] : 1;
|
||||
$this->condition_type = $data['condition_type'] ?? '';
|
||||
$this->condition_value = $data['condition_value'] ?? '';
|
||||
$this->action_type = $data['action_type'] ?? '';
|
||||
$this->action_value = $data['action_value'] ?? '';
|
||||
$this->created_at = $data['created_at'] ?? null;
|
||||
$this->updated_at = $data['updated_at'] ?? null;
|
||||
|
||||
if (empty($this->conditions) && !empty($this->condition_type)) {
|
||||
$this->conditions = [
|
||||
['type' => $this->condition_type, 'value' => $this->condition_value],
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($this->actions) && !empty($this->action_type)) {
|
||||
$this->actions = [
|
||||
['type' => $this->action_type, 'value' => $this->action_value],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function parseJsonField($value) {
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array
|
||||
*/
|
||||
public function toArray() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'conditions' => wp_json_encode($this->conditions),
|
||||
'actions' => wp_json_encode($this->actions),
|
||||
'priority' => $this->priority,
|
||||
'start_date' => $this->start_date,
|
||||
'end_date' => $this->end_date,
|
||||
'enabled' => $this->enabled,
|
||||
'condition_type' => $this->condition_type,
|
||||
'condition_value' => $this->condition_value,
|
||||
'action_type' => $this->action_type,
|
||||
'action_value' => $this->action_value,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
76
app/Repositories/RuleRepository.php
Normal file
76
app/Repositories/RuleRepository.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Sodino\Repositories;
|
||||
|
||||
use Sodino\Models\Rule;
|
||||
|
||||
/**
|
||||
* Rule Repository
|
||||
*/
|
||||
class RuleRepository {
|
||||
private $table_name;
|
||||
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$this->table_name = $wpdb->prefix . 'sodino_rules';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rules
|
||||
*/
|
||||
public function getAll() {
|
||||
global $wpdb;
|
||||
$results = $wpdb->get_results("SELECT * FROM {$this->table_name} ORDER BY priority DESC, id ASC", ARRAY_A);
|
||||
$rules = [];
|
||||
foreach ($results as $result) {
|
||||
$rules[] = new Rule($result);
|
||||
}
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rule by ID
|
||||
*/
|
||||
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 Rule($result) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enabled rules
|
||||
*/
|
||||
public function getEnabled() {
|
||||
global $wpdb;
|
||||
$results = $wpdb->get_results("SELECT * FROM {$this->table_name} WHERE enabled = 1 ORDER BY priority DESC, id ASC", ARRAY_A);
|
||||
$rules = [];
|
||||
foreach ($results as $result) {
|
||||
$rules[] = new Rule($result);
|
||||
}
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save rule
|
||||
*/
|
||||
public function save(Rule $rule) {
|
||||
global $wpdb;
|
||||
$data = $rule->toArray();
|
||||
unset($data['id'], $data['created_at'], $data['updated_at']);
|
||||
|
||||
if ($rule->id) {
|
||||
$wpdb->update($this->table_name, $data, ['id' => $rule->id]);
|
||||
return $rule->id;
|
||||
} else {
|
||||
$wpdb->insert($this->table_name, $data);
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete rule
|
||||
*/
|
||||
public function delete($id) {
|
||||
global $wpdb;
|
||||
return $wpdb->delete($this->table_name, ['id' => $id]);
|
||||
}
|
||||
}
|
||||
210
app/Services/PricingService.php
Normal file
210
app/Services/PricingService.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
namespace Sodino\Services;
|
||||
|
||||
use Sodino\Repositories\RuleRepository;
|
||||
|
||||
/**
|
||||
* Pricing Service
|
||||
*/
|
||||
class PricingService {
|
||||
private $ruleRepository;
|
||||
private $rulesCache = null;
|
||||
private $freeShipping = false;
|
||||
|
||||
public function __construct(RuleRepository $ruleRepository) {
|
||||
$this->ruleRepository = $ruleRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply dynamic pricing to a product price
|
||||
*/
|
||||
public function applyDynamicPricing($price, $product) {
|
||||
$price = $this->normalizePrice($price);
|
||||
$rules = $this->getEnabledRules();
|
||||
$matchedRule = null;
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
if ($this->ruleMatches($rule, $product)) {
|
||||
if ($matchedRule === null || $rule->priority > $matchedRule->priority) {
|
||||
$matchedRule = $rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($matchedRule) {
|
||||
$price = $this->applyActions($matchedRule, $price);
|
||||
}
|
||||
|
||||
return max(0, $price);
|
||||
}
|
||||
|
||||
public function shouldApplyFreeShipping() {
|
||||
$rules = $this->getEnabledRules();
|
||||
foreach ($rules as $rule) {
|
||||
if ($this->ruleMatches($rule, null) && $this->ruleHasFreeShipping($rule)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function resetFreeShippingFlag() {
|
||||
$this->freeShipping = false;
|
||||
}
|
||||
|
||||
private function getEnabledRules() {
|
||||
if ($this->rulesCache === null) {
|
||||
$this->rulesCache = $this->ruleRepository->getEnabled();
|
||||
}
|
||||
return $this->rulesCache;
|
||||
}
|
||||
|
||||
private function normalizePrice($price) {
|
||||
if ($price === '' || $price === null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return floatval($price);
|
||||
}
|
||||
|
||||
private function getUserType() {
|
||||
if (!is_user_logged_in()) {
|
||||
return 'guest';
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$order_count = wc_get_customer_order_count($user_id);
|
||||
|
||||
return $order_count > 0 ? 'returning' : 'new';
|
||||
}
|
||||
|
||||
private function ruleMatches($rule, $product = null) {
|
||||
if (!$this->isRuleActive($rule)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($rule->conditions as $condition) {
|
||||
if (!$this->evaluateCondition($condition, $product)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isRuleActive($rule) {
|
||||
if (!$rule->enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = current_time('Y-m-d H:i:s');
|
||||
|
||||
if (!empty($rule->start_date) && $now < $rule->start_date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($rule->end_date) && $now > $rule->end_date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function evaluateCondition($condition, $product = null) {
|
||||
$type = $condition['type'] ?? '';
|
||||
$value = $condition['value'] ?? null;
|
||||
|
||||
switch ($type) {
|
||||
case 'user_type':
|
||||
return $this->getUserType() === $value;
|
||||
case 'cart_total_min':
|
||||
return $this->getCartTotal() >= floatval($value);
|
||||
case 'cart_total_max':
|
||||
return $this->getCartTotal() <= floatval($value);
|
||||
case 'cart_item_count_min':
|
||||
return $this->getCartItemCount() >= intval($value);
|
||||
case 'cart_item_count_max':
|
||||
return $this->getCartItemCount() <= intval($value);
|
||||
case 'product_category':
|
||||
return $this->productHasCategory($product, (array) $value);
|
||||
case 'product_ids':
|
||||
return $this->productIsInIds($product, (array) $value);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCartTotal() {
|
||||
if (!WC()->cart) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return floatval(WC()->cart->get_cart_contents_total());
|
||||
}
|
||||
|
||||
private function getCartItemCount() {
|
||||
if (!WC()->cart) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return WC()->cart->get_cart_contents_count();
|
||||
}
|
||||
|
||||
private function productHasCategory($product, $categories) {
|
||||
if (!$product || empty($categories)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$product_cats = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'ids']);
|
||||
return (bool) array_intersect($product_cats, $categories);
|
||||
}
|
||||
|
||||
private function productIsInIds($product, $ids) {
|
||||
if (!$product || empty($ids)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($product->get_id(), $ids, true);
|
||||
}
|
||||
|
||||
private function applyActions($rule, $price) {
|
||||
foreach ($rule->actions as $action) {
|
||||
$price = $this->applyAction($action, $price);
|
||||
if (($action['type'] ?? '') === 'free_shipping') {
|
||||
$this->freeShipping = true;
|
||||
}
|
||||
}
|
||||
return $price;
|
||||
}
|
||||
|
||||
private function ruleHasFreeShipping($rule) {
|
||||
foreach ($rule->actions as $action) {
|
||||
if (($action['type'] ?? '') === 'free_shipping') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function applyAction($action, $price) {
|
||||
$type = $action['type'] ?? '';
|
||||
$value = isset($action['value']) ? floatval($action['value']) : 0;
|
||||
|
||||
switch ($type) {
|
||||
case 'discount_percent':
|
||||
if ($value <= 0) {
|
||||
return $price;
|
||||
}
|
||||
return $price * (1 - $value / 100);
|
||||
case 'discount_fixed':
|
||||
if ($value <= 0) {
|
||||
return $price;
|
||||
}
|
||||
return $price - $value;
|
||||
case 'free_shipping':
|
||||
return $price;
|
||||
default:
|
||||
return $price;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user