Init(Core): create and add project

This commit is contained in:
2026-05-02 01:58:10 +03:30
commit 4928901a08
14 changed files with 1022 additions and 0 deletions

28
admin/admin.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
use Sodino\Controllers\AdminController;
use Sodino\Repositories\RuleRepository;
// Initialize admin
$ruleRepository = new RuleRepository();
$adminController = new AdminController($ruleRepository);
// Add menu
add_action('admin_menu', [$adminController, 'addMenu']);
// Enqueue admin assets
add_action('admin_enqueue_scripts', function($hook) {
if (strpos($hook, 'sodino') === false) {
return;
}
wp_enqueue_style('sodino-admin', plugin_dir_url(__FILE__) . 'css/admin.css', [], SODINO_VERSION);
});
// Handle delete for any Sodino admin page
if (isset($_GET['page']) && strpos($_GET['page'], 'sodino') === 0 && isset($_GET['action']) && $_GET['action'] === 'delete') {
add_action('admin_init', [$adminController, 'handleDelete']);
}

View File

@@ -0,0 +1,132 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
if (!class_exists('WP_List_Table')) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
class Sodino_Rules_List_Table extends WP_List_Table {
private $repository;
private $items_per_page = 20;
public function __construct($repository) {
parent::__construct([
'singular' => 'sodino_rule',
'plural' => 'sodino_rules',
'ajax' => false,
]);
$this->repository = $repository;
}
public function get_columns() {
return [
'cb' => '<input type="checkbox" />',
'name' => __('عنوان قانون', 'sodino'),
'condition_type' => __('نوع کاربر', 'sodino'),
'action_value' => __('درصد تخفیف', 'sodino'),
'enabled' => __('وضعیت', 'sodino'),
'actions' => __('عملیات', 'sodino'),
];
}
protected function get_sortable_columns() {
return [
'name' => ['name', true],
];
}
protected function column_cb($item) {
return sprintf('<input type="checkbox" name="rule_ids[]" value="%d" />', $item->id);
}
public function get_bulk_actions() {
return [
'delete' => __('حذف گروهی', 'sodino'),
];
}
public function column_actions($item) {
$edit_url = admin_url('admin.php?page=sodino-add-rule&action=edit&id=' . $item->id);
$delete_url = wp_nonce_url(admin_url('admin.php?page=sodino-rules&action=delete&id=' . $item->id), 'delete_rule');
return sprintf(
'<a href="%s">%s</a> | <a href="%s" onclick="return confirm(\'%s\');">%s</a>',
esc_url($edit_url),
esc_html__('ویرایش', 'sodino'),
esc_url($delete_url),
esc_js(__('آیا از حذف این قانون مطمئن هستید؟', 'sodino')),
esc_html__('حذف', 'sodino')
);
}
public function column_name($item) {
$edit_url = admin_url('admin.php?page=sodino-add-rule&action=edit&id=' . $item->id);
$title = sprintf('<strong><a href="%s">%s</a></strong>', esc_url($edit_url), esc_html($item->name));
return $title;
}
public function column_condition_type($item) {
$value = __('کاربر جدید', 'sodino');
if ($item->condition_value === 'returning') {
$value = __('کاربر بازگشتی', 'sodino');
}
return esc_html($value);
}
public function column_action_value($item) {
return sprintf('%s %%', esc_html($item->action_value));
}
public function column_enabled($item) {
return $item->enabled ? __('فعال', 'sodino') : __('غیرفعال', 'sodino');
}
public function column_default($item, $column_name) {
switch ($column_name) {
case 'name':
case 'condition_type':
case 'action_value':
case 'enabled':
case 'actions':
return '';
default:
return '';
}
}
public function prepare_items() {
$columns = $this->get_columns();
$hidden = [];
$sortable = $this->get_sortable_columns();
$this->_column_headers = [$columns, $hidden, $sortable];
$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()) {
$rule_ids = isset($_POST['rule_ids']) ? array_map('intval', $_POST['rule_ids']) : [];
if (!empty($rule_ids) && check_admin_referer('bulk-' . $this->_args['plural'])) {
foreach ($rule_ids as $id) {
$this->repository->delete($id);
}
}
}
}
}

22
admin/css/admin.css Normal file
View File

@@ -0,0 +1,22 @@
.wrap {
direction: rtl;
}
.sodino-admin-table th,
.sodino-admin-table td {
text-align: right;
}
.page-title-action {
float: left;
}
.rtl .page-title-action {
float: left;
}
input.regular-text,
select.regular-text,
input.small-text {
direction: rtl;
}

54
admin/views/rule-form.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="wrap">
<h1><?php echo $rule->id ? __('ویرایش قانون', 'sodino') : __('افزودن قانون جدید', 'sodino'); ?></h1>
<form method="post">
<?php wp_nonce_field('gheymatyar_save_rule', 'gheymatyar_rule_nonce'); ?>
<table class="form-table">
<tr>
<th scope="row"><label for="name"><?php _e('عنوان قانون', 'sodino'); ?></label></th>
<td><input type="text" name="name" id="name" value="<?php echo esc_attr($rule->name); ?>" class="regular-text" required></td>
</tr>
<tr>
<th scope="row"><label for="condition_type"><?php _e('نوع شرط', 'sodino'); ?></label></th>
<td>
<select name="condition_type" id="condition_type" class="regular-text" required>
<option value="user_type" <?php selected($rule->condition_type, 'user_type'); ?>><?php _e('نوع کاربر', 'sodino'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row"><label for="condition_value"><?php _e('نوع کاربر', 'sodino'); ?></label></th>
<td>
<select name="condition_value" id="condition_value" class="regular-text" required>
<option value="new" <?php selected($rule->condition_value, 'new'); ?>><?php _e('کاربر جدید', 'sodino'); ?></option>
<option value="returning" <?php selected($rule->condition_value, 'returning'); ?>><?php _e('کاربر بازگشتی', 'sodino'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row"><label for="action_type"><?php _e('نوع عملیات', 'sodino'); ?></label></th>
<td>
<select name="action_type" id="action_type" class="regular-text" required>
<option value="discount_percent" <?php selected($rule->action_type, 'discount_percent'); ?>><?php _e('درصد تخفیف', 'sodino'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row"><label for="action_value"><?php _e('درصد تخفیف', 'sodino'); ?></label></th>
<td><input type="number" name="action_value" id="action_value" value="<?php echo esc_attr($rule->action_value); ?>" min="0" max="100" step="0.01" class="small-text" required> %</td>
</tr>
<tr>
<th scope="row"><label for="enabled"><?php _e('وضعیت', 'sodino'); ?></label></th>
<td><label><input type="checkbox" name="enabled" id="enabled" value="1" <?php checked($rule->enabled, 1); ?>> <?php _e('فعال باشد', 'sodino'); ?></label></td>
</tr>
</table>
<?php submit_button($rule->id ? __('به‌روزرسانی قانون', 'sodino') : __('افزودن قانون', 'sودino'), 'primary'); ?>
</form>
</div>

View File

@@ -0,0 +1,15 @@
<?php
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="wrap">
<h1><?php _e('قوانین قیمت‌گذاری', 'sodino'); ?></h1>
<a href="<?php echo admin_url('admin.php?page=sodino-add-rule'); ?>" class="page-title-action"><?php _e('افزودن قانون جدید', 'sodino'); ?></a>
<form method="post">
<?php $rulesTable->display(); ?>
</form>
</div>

12
admin/views/settings.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="wrap">
<h1><?php _e('تنظیمات قیمت‌یار', 'sodino'); ?></h1>
<div class="notice notice-info">
<p><?php _e('در اینجا تنظیمات افزونه در آینده قرار خواهد گرفت.', 'sodino'); ?></p>
</div>
</div>

View 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
View 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,
];
}
}

View 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]);
}
}

View 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;
}
}
}

58
database/migrations.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Database migrations for Sodino plugin
*/
function sodino_create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Rules table
$rules_table = $wpdb->prefix . 'sodino_rules';
$rules_sql = "CREATE TABLE $rules_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
conditions longtext NOT NULL,
actions longtext NOT NULL,
priority int(11) NOT NULL DEFAULT 10,
start_date datetime NULL,
end_date datetime NULL,
enabled tinyint(1) DEFAULT 1,
condition_type varchar(100) DEFAULT NULL,
condition_value varchar(255) DEFAULT NULL,
action_type varchar(100) DEFAULT NULL,
action_value varchar(255) DEFAULT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// Upsell table
$upsell_table = $wpdb->prefix . 'sodino_upsells';
$upsell_sql = "CREATE TABLE $upsell_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
triggers longtext NOT NULL,
suggestions longtext NOT NULL,
discount_type varchar(50) DEFAULT 'percentage',
discount_value varchar(50) DEFAULT '0',
enabled tinyint(1) DEFAULT 1,
priority int(11) NOT NULL DEFAULT 10,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($rules_sql);
dbDelta($upsell_sql);
// Add version option
add_option('sodino_db_version', '1.1');
}

View File

@@ -0,0 +1,34 @@
<?php
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
use Sodino\Services\PricingService;
use Sodino\Repositories\RuleRepository;
// Initialize pricing service
global $sodino_pricing_service;
$ruleRepository = new RuleRepository();
$sodino_pricing_service = new PricingService($ruleRepository);
// Hook into WooCommerce price filter
add_filter('woocommerce_product_get_price', 'sodino_apply_dynamic_pricing', 10, 2);
add_filter('woocommerce_product_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
add_filter('woocommerce_product_variation_get_price', 'sodino_apply_dynamic_pricing', 10, 2);
add_filter('woocommerce_product_variation_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
// Also hook into cart and checkout prices
add_filter('woocommerce_cart_item_price', 'sodino_apply_to_cart_item', 10, 3);
add_filter('woocommerce_cart_item_subtotal', 'sodino_apply_to_cart_item', 10, 3);
function sodino_apply_dynamic_pricing($price, $product) {
global $sodino_pricing_service;
return $sodino_pricing_service->applyDynamicPricing($price, $product);
}
function sodino_apply_to_cart_item($price, $cart_item, $cart_item_key) {
global $sodino_pricing_service;
$product = $cart_item['data'];
return wc_price($sodino_pricing_service->applyDynamicPricing($product->get_price(), $product));
}

38
readme.txt Normal file
View File

@@ -0,0 +1,38 @@
=== Sodino ===
Contributors: yourname
Tags: woocommerce, pricing, dynamic pricing, revenue optimization
Requires at least: 5.0
Tested up to: 6.0
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Smart Pricing & Revenue Optimization plugin for WooCommerce.
== Description ==
Sodino dynamically adjusts WooCommerce product prices based on user behavior and predefined rules.
Features:
- Dynamic pricing based on user type (new vs returning)
- Rule-based system for discounts
- Admin panel to manage rules
- MVC architecture for extensibility
== Installation ==
1. Upload the plugin files to the `/wp-content/plugins/sodino` directory, or install the plugin through the WordPress plugins screen directly.
2. Activate the plugin through the 'Plugins' screen in WordPress.
3. Use the Sodino menu in the admin to configure rules.
== Frequently Asked Questions ==
= Does it work with variable products? =
Yes, it applies to all product types.
== Changelog ==
= 1.0.0 =
* Initial release.

100
sodino.php Normal file
View File

@@ -0,0 +1,100 @@
<?php
/**
* Plugin Name: Sodino (سودینو)
* Plugin URI: https://example.com/sodino
* Description: افزونه هوشمند قیمت‌گذاری و بهینه‌سازی درآمد برای ووکامرس. قیمت محصولات را بر اساس رفتار کاربر و قوانین تعریف‌شده به صورت پویا تنظیم می‌کند.
* Version: 1.0.0
* Author: Your Name
* License: GPL v2 or later
* Text Domain: sodino
* Requires at least: 5.0
* Tested up to: 6.0
* Requires PHP: 7.4
* WC requires at least: 5.0
* WC tested up to: 6.0
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Define plugin constants
define('SODINO_VERSION', '1.0.0');
define('SODINO_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('SODINO_PLUGIN_URL', plugin_dir_url(__FILE__));
define('SODINO_PLUGIN_BASENAME', plugin_basename(__FILE__));
// Autoloader for PSR-4
spl_autoload_register(function ($class) {
// Namespace prefix
$prefix = 'Sodino\\';
// Base directory for the namespace prefix
$base_dir = SODINO_PLUGIN_DIR . 'app/';
// Does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
// Get the relative class name
$relative_class = substr($class, $len);
// Replace namespace separators with directory separators
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// If the file exists, require it
if (file_exists($file)) {
require $file;
}
});
// Activation hook
register_activation_hook(__FILE__, 'sodino_activate');
function sodino_activate() {
// Include database migration
require_once SODINO_PLUGIN_DIR . 'database/migrations.php';
sodino_create_tables();
// Flush rewrite rules if needed
flush_rewrite_rules();
}
// Deactivation hook
register_deactivation_hook(__FILE__, 'sodino_deactivate');
function sodino_deactivate() {
// Flush rewrite rules
flush_rewrite_rules();
}
// Bootstrap the plugin
function sodino_init() {
// Check if WooCommerce is active
if (!class_exists('WooCommerce')) {
add_action('admin_notices', 'sodino_woocommerce_missing_notice');
return;
}
// Initialize admin
if (is_admin()) {
require_once SODINO_PLUGIN_DIR . 'admin/admin.php';
}
// Initialize public hooks
require_once SODINO_PLUGIN_DIR . 'public/hooks/pricing-hooks.php';
// Load text domain
load_plugin_textdomain('sodino', false, dirname(SODINO_PLUGIN_BASENAME) . '/languages/');
}
add_action('plugins_loaded', 'sodino_init');
// WooCommerce missing notice
function sodino_woocommerce_missing_notice() {
?>
<div class="error">
<p><?php _e('Sodino requires WooCommerce to be installed and active.', 'sodino'); ?></p>
</div>
<?php
}