update structure
This commit is contained in:
parent
17e4eea464
commit
dc26d50c6b
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
add_action('init', function () {
|
||||
|
||||
if (!function_exists('wc_create_attribute')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attribute_name = 'Source';
|
||||
$attribute_slug = 'source';
|
||||
$taxonomy = 'pa_' . $attribute_slug;
|
||||
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$exists = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT attribute_id
|
||||
FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
|
||||
WHERE attribute_name = %s",
|
||||
$attribute_slug
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
if (!$exists) {
|
||||
wc_create_attribute([
|
||||
'name' => $attribute_name,
|
||||
'slug' => $attribute_slug,
|
||||
'type' => 'select',
|
||||
'order_by' => 'menu_order',
|
||||
'has_archives' => false,
|
||||
]);
|
||||
|
||||
// Clear cache Woo
|
||||
delete_transient('wc_attribute_taxonomies');
|
||||
}
|
||||
|
||||
|
||||
if (!taxonomy_exists($taxonomy)) {
|
||||
register_taxonomy(
|
||||
$taxonomy,
|
||||
'product',
|
||||
[
|
||||
'label' => $attribute_name,
|
||||
'public' => false,
|
||||
'hierarchical' => false,
|
||||
|
||||
'show_ui' => true,
|
||||
'query_var' => true,
|
||||
'rewrite' => false,
|
||||
|
||||
// Cho phép gán trong Product data → Attributes
|
||||
'meta_box_cb' => 'post_tags_meta_box',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
foreach (['on_store', 'links'] as $term) {
|
||||
if (!term_exists($term, $taxonomy)) {
|
||||
wp_insert_term($term, $taxonomy);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
add_action('woocommerce_order_status_changed', function ($order_id, $old, $new, $order) {
|
||||
|
||||
// Chỉ khi chuyển sang completed
|
||||
if ($new !== 'completed') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$order instanceof WC_Order) {
|
||||
$order = wc_get_order($order_id);
|
||||
}
|
||||
if (!$order) return;
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($order->get_items() as $item_id => $item) {
|
||||
/** @var WC_Order_Item_Product $item */
|
||||
$product = $item->get_product();
|
||||
|
||||
$items[] = [
|
||||
'item_id' => $item_id,
|
||||
'product_id' => $product ? $product->get_id() : null,
|
||||
'variation_id' => $item->get_variation_id(),
|
||||
'type' => $product ? $product->get_type() : null,
|
||||
'name' => $item->get_name(),
|
||||
'sku' => $product ? $product->get_sku() : null,
|
||||
'quantity' => $item->get_quantity(),
|
||||
|
||||
// Giá
|
||||
'subtotal' => $item->get_subtotal(),
|
||||
'total' => $item->get_total(),
|
||||
'tax' => $item->get_total_tax(),
|
||||
|
||||
// Giá đơn vị
|
||||
'price_per_item' => $item->get_quantity() > 0
|
||||
? round($item->get_total() / $item->get_quantity(), 2)
|
||||
: 0,
|
||||
|
||||
// Attributes (variation)
|
||||
'attributes' => $item->get_variation_id()
|
||||
? wc_get_formatted_variation($product, true, false, true)
|
||||
: null,
|
||||
|
||||
// Meta item
|
||||
'item_meta' => array_map(function ($meta) {
|
||||
return [
|
||||
'key' => $meta->key,
|
||||
'value' => $meta->value,
|
||||
];
|
||||
}, $item->get_meta_data()),
|
||||
];
|
||||
}
|
||||
|
||||
$log = [
|
||||
'event' => 'order_completed',
|
||||
'order_id' => $order_id,
|
||||
'old_status' => $old,
|
||||
'new_status' => $new,
|
||||
'currency' => $order->get_currency(),
|
||||
'total' => $order->get_total(),
|
||||
'items' => $items,
|
||||
];
|
||||
|
||||
WPI_Logger::log(json_encode(
|
||||
$log,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||
));
|
||||
}, 10, 4);
|
||||
|
|
@ -115,4 +115,77 @@ class WPI_Category_Mapper
|
|||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public static function map_categories_to_tree(
|
||||
array $categories,
|
||||
$rootParentId = '0',
|
||||
array $ignoreIds = []
|
||||
) {
|
||||
$map = [];
|
||||
$roots = [];
|
||||
|
||||
// Map ignore
|
||||
$ignoreMap = array_flip(array_map('strval', $ignoreIds));
|
||||
|
||||
// Map id => parentId để truy ngược nhanh
|
||||
$parentLookup = [];
|
||||
foreach ($categories as $cat) {
|
||||
$parentLookup[(string) $cat['id']] = (string) $cat['parentId'];
|
||||
}
|
||||
|
||||
// Step 1: tạo node
|
||||
foreach ($categories as $item) {
|
||||
if (isset($ignoreMap[(string) $item['id']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[(string) $item['id']] = [
|
||||
'id' => (int) $item['id'],
|
||||
'name' => trim($item['name']),
|
||||
'level' => 0,
|
||||
'parent_id' => (int) $item['parentId'],
|
||||
'category_origin_id' => '',
|
||||
'group_category_id' => null,
|
||||
'is_active' => 1,
|
||||
'value' => (int) $item['id'],
|
||||
'label' => trim($item['name']),
|
||||
'children' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Step 2: gắn children
|
||||
foreach ($map as $id => &$node) {
|
||||
|
||||
$parentId = (string) $node['parent_id'];
|
||||
|
||||
// Nếu parent bị ignore → leo lên cha tiếp theo
|
||||
while (
|
||||
isset($ignoreMap[$parentId]) &&
|
||||
isset($parentLookup[$parentId])
|
||||
) {
|
||||
$parentId = $parentLookup[$parentId];
|
||||
}
|
||||
|
||||
if ($parentId == (string) $rootParentId || !isset($map[$parentId])) {
|
||||
$roots[] = &$node;
|
||||
} else {
|
||||
$map[$parentId]['children'][] = &$node;
|
||||
}
|
||||
}
|
||||
unset($node);
|
||||
|
||||
// Step 3: set level
|
||||
$setLevel = function (&$nodes, $level) use (&$setLevel) {
|
||||
foreach ($nodes as &$node) {
|
||||
$node['level'] = $level;
|
||||
if (!empty($node['children'])) {
|
||||
$setLevel($node['children'], $level + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$setLevel($roots, 1);
|
||||
|
||||
return $roots;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,22 @@ class WPI_API_Importer
|
|||
|
||||
private static function api_url($url)
|
||||
{
|
||||
$base_url = WPI_Config_Model::get_config('base_api');
|
||||
return $base_url . $url;
|
||||
if (empty($url)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Nếu đã là absolute URL (http / https)
|
||||
$parsed = parse_url($url);
|
||||
if (isset($parsed['scheme']) && in_array($parsed['scheme'], ['http', 'https'], true)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$base_url = rtrim(WPI_Config_Model::get_config('base_api'), '/');
|
||||
|
||||
return $base_url . ltrim($url, '/');
|
||||
}
|
||||
|
||||
|
||||
public static function products(array $args = [])
|
||||
{
|
||||
$page = isset($args['page'])
|
||||
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
class WPI_Cron
|
||||
{
|
||||
const HOOK = 'wpi_import_products_cron';
|
||||
const TRIGGER_HOOK = 'wpi_trigger_import_cron';
|
||||
const SYNC_CATEGORIES_HOOK = 'wpi_sync_categories_cron';
|
||||
const IMPORT_PRODUCTS_HOOK = 'wpi_import_products_cron';
|
||||
const TRIGGER_IMPORT_PRODUCTS_HOOK = 'wpi_trigger_import_cron';
|
||||
const IMPORT_CATEGORIES_HOOK = 'wpi_sync_categories_cron';
|
||||
const DISPATCH_HOOK = 'wpi_dispatch_import_workers';
|
||||
|
||||
public static function init()
|
||||
{
|
||||
add_filter('cron_schedules', [self::class, 'add_schedule']);
|
||||
add_action(
|
||||
self::HOOK,
|
||||
[self::class, 'run'],
|
||||
self::IMPORT_PRODUCTS_HOOK,
|
||||
[self::class, 'import_products'],
|
||||
10,
|
||||
1
|
||||
);
|
||||
add_action(self::DISPATCH_HOOK, [self::class, 'dispatch_workers']);
|
||||
add_action(self::TRIGGER_HOOK, [self::class, 'trigger']);
|
||||
add_action(self::SYNC_CATEGORIES_HOOK, [self::class, 'sync_categories']);
|
||||
add_action(self::TRIGGER_IMPORT_PRODUCTS_HOOK, [self::class, 'trigger_import_products']);
|
||||
add_action(self::IMPORT_CATEGORIES_HOOK, [self::class, 'import_categories']);
|
||||
}
|
||||
|
||||
public static function add_schedule($schedules)
|
||||
|
|
@ -39,24 +39,26 @@ class WPI_Cron
|
|||
// }
|
||||
|
||||
// // Trigger cron (2h sáng mỗi ngày)
|
||||
// if (!wp_next_scheduled(self::TRIGGER_HOOK)) {
|
||||
// if (!wp_next_scheduled(self::TRIGGER_IMPORT_PRODUCTS_HOOK)) {
|
||||
// $timestamp = strtotime('tomorrow 02:00');
|
||||
|
||||
// wp_schedule_event(
|
||||
// $timestamp,
|
||||
// 'daily',
|
||||
// self::TRIGGER_HOOK
|
||||
// self::TRIGGER_IMPORT_PRODUCTS_HOOK
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
public static function deactivate()
|
||||
{
|
||||
wp_clear_scheduled_hook(self::HOOK);
|
||||
wp_clear_scheduled_hook(self::TRIGGER_HOOK);
|
||||
wp_clear_scheduled_hook(self::IMPORT_PRODUCTS_HOOK);
|
||||
wp_clear_scheduled_hook(self::TRIGGER_IMPORT_PRODUCTS_HOOK);
|
||||
wp_clear_scheduled_hook(self::IMPORT_CATEGORIES_HOOK);
|
||||
wp_clear_scheduled_hook(self::DISPATCH_HOOK);
|
||||
}
|
||||
|
||||
public static function run(array $args = [])
|
||||
public static function import_products(array $args = [])
|
||||
{
|
||||
|
||||
$worker = $args['worker'] ?? 'default';
|
||||
|
|
@ -165,6 +167,8 @@ class WPI_Cron
|
|||
}
|
||||
|
||||
WPI_Logger::log("Page {$page} done");
|
||||
} catch (\Throwable $th) {
|
||||
WPI_Config_Model::set_config('status_workers', 0);
|
||||
} finally {
|
||||
WPI_Config_Model::set_config('sync_status_' . $worker, 0);
|
||||
}
|
||||
|
|
@ -212,7 +216,7 @@ class WPI_Cron
|
|||
}
|
||||
|
||||
|
||||
public static function trigger()
|
||||
public static function trigger_import_products()
|
||||
{
|
||||
if (WPI_Config_Model::get_config('status_workers')) {
|
||||
WPI_Logger::log('Trigger skipped: sync is running');
|
||||
|
|
@ -226,7 +230,7 @@ class WPI_Cron
|
|||
}
|
||||
|
||||
|
||||
public static function sync_categories()
|
||||
public static function import_categories()
|
||||
{
|
||||
if (!class_exists('WooCommerce')) {
|
||||
WPI_Logger::log('WooCommerce not active');
|
||||
|
|
@ -266,7 +270,13 @@ class WPI_Cron
|
|||
|
||||
$json = json_decode($body, true);
|
||||
|
||||
if (empty($json['status']) || empty($json['data']) || !is_array($json['data'])) {
|
||||
|
||||
// if (empty($json['status']) || empty($json['data']) || !is_array($json['data'])) {
|
||||
// WPI_Logger::log('Invalid API response structure');
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (empty($json['code']) || empty($json['data']) || !is_array($json['data'])) {
|
||||
WPI_Logger::log('Invalid API response structure');
|
||||
return;
|
||||
}
|
||||
|
|
@ -274,8 +284,10 @@ class WPI_Cron
|
|||
WPI_Logger::log('Fetched ' . count($json['data']) . ' root categories');
|
||||
|
||||
|
||||
$data = WPI_Category_Mapper::map_categories_to_tree($json['data']);
|
||||
|
||||
WPI_Category_Mapper::upsert_wp_categories_from_tree(
|
||||
$json['data'],
|
||||
$data,
|
||||
0,
|
||||
'product_cat'
|
||||
);
|
||||
|
|
@ -28,9 +28,24 @@
|
|||
|
||||
- CLI WP
|
||||
|
||||
# Xoá toàn bộ products
|
||||
# Xoá dữ liệu
|
||||
|
||||
wp --allow-root post delete $(wp --allow-root post list --post_type=product,product_variation --format=ids) --force
|
||||
<!-- Products -->
|
||||
|
||||
- wp --allow-root post delete $(wp --allow-root post list --post_type=product,product_variation --format=ids) --force
|
||||
|
||||
<!-- Categories -->
|
||||
|
||||
- wp term delete product_cat \
|
||||
$(wp term list product_cat --field=term_id --allow-root) \
|
||||
--allow-root
|
||||
|
||||
<!-- Medias -->
|
||||
|
||||
- wp post delete \
|
||||
$(wp post list --post_type=attachment --format=ids --allow-root) \
|
||||
--force \
|
||||
--allow-root
|
||||
|
||||
# Cài WP CLI
|
||||
|
||||
|
|
@ -38,3 +53,7 @@ curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.pha
|
|||
php wp-cli.phar --info
|
||||
chmod +x wp-cli.phar
|
||||
mv wp-cli.phar /usr/local/bin/wp
|
||||
|
||||
# Tìm kiếm tiến trình
|
||||
|
||||
ps aux | grep "wp eval" | grep "wpi_import_products_cron" | grep -v grep
|
||||
|
|
|
|||
|
|
@ -21,23 +21,33 @@ define('WPI_URL', plugin_dir_url(__FILE__));
|
|||
* =========================
|
||||
*/
|
||||
|
||||
// Init
|
||||
require_once WPI_PATH . 'includes/configs/wpi-init.php';
|
||||
|
||||
// Migrations
|
||||
require_once WPI_PATH . 'includes/migrations/create-config-table.php';
|
||||
|
||||
// Models
|
||||
require_once WPI_PATH . 'includes/models/class-wpi-config-model.php';
|
||||
require_once WPI_PATH . 'includes/models/wpi-config-model.php';
|
||||
|
||||
// Helper
|
||||
require_once WPI_PATH . 'includes/helpers/class-product-helper.php';
|
||||
require_once WPI_PATH . 'includes/helpers/class-category-helper.php';
|
||||
require_once WPI_PATH . 'includes/helpers/wpi-product-helper.php';
|
||||
require_once WPI_PATH . 'includes/helpers/wpi-category-helper.php';
|
||||
|
||||
// Mappings
|
||||
require_once WPI_PATH . 'includes/mappings/wpi-product-map.php';
|
||||
|
||||
|
||||
// Core WPIS
|
||||
require_once WPI_PATH . 'includes/wpi-api-importer.php';
|
||||
require_once WPI_PATH . 'includes/wpi-cron.php';
|
||||
require_once WPI_PATH . 'includes/wpi-logger.php';
|
||||
require_once WPI_PATH . 'includes/wpi-product-validator.php';
|
||||
|
||||
// Events
|
||||
require_once WPI_PATH . 'includes/events/wpi-order-event.php';
|
||||
|
||||
|
||||
// Core classes
|
||||
require_once WPI_PATH . 'includes/class-api-importer.php';
|
||||
require_once WPI_PATH . 'includes/class-cron.php';
|
||||
require_once WPI_PATH . 'includes/class-logger.php';
|
||||
require_once WPI_PATH . 'includes/class-product-map.php';
|
||||
require_once WPI_PATH . 'includes/class-product-validator.php';
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -53,9 +63,10 @@ add_action('plugins_loaded', function () {
|
|||
// }
|
||||
|
||||
// Init cron only if WooCommerce active
|
||||
if (class_exists('WooCommerce')) {
|
||||
WPI_Cron::init();
|
||||
}
|
||||
// if (class_exists('WooCommerce')) {
|
||||
// WPI_Cron::init();
|
||||
// }
|
||||
WPI_Cron::init();
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -89,7 +100,8 @@ register_activation_hook(__FILE__, function () {
|
|||
// Default configs
|
||||
WPI_Config_Model::set_config(
|
||||
'categories_api',
|
||||
'/category',
|
||||
'https://prology.net/rest/V1/client/get-all-categories',
|
||||
// '/category',
|
||||
'Api path to get categories'
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue