إذا سبق لك أن قضيت 40 دقيقة في كتابة وصف منتج لتنتهي بنسخة فاترة، فستحب سير العمل هذا: يقوم WooCommerce بإنشاء وصف "طويل" ومقتطف "قصير" من العنوان والخصائص وبعض الملاحظات التجارية - وتحتفظ أنت بالتحكم في عملية التحقق.

الحاجة / حالة الاستخدام

في كتالوجات ووكومرس، غالبًا ما يُمثّل إنشاء المحتوى العائق الرئيسي. لقد رأيت متاجر تضم 300 منتج "معلق" لمجرد نقص الأوصاف، على الرغم من جاهزية الصور والأسعار. والنتيجة: ضعف في تحسين محركات البحث، وانخفاض حاد في معدلات التحويل، وتأخير في إطلاق المنتج.

يُعدّ الذكاء الاصطناعي مفيدًا هنا لإنتاج مسودة أولية متماسكة ومنظمة وموجهة نحو الربح من البيانات المتاحة بالفعل في WordPress : العنوان، والفئات، والخصائص، والعلامة التجارية، والأبعاد، والمواد، وما إلى ذلك. الهدف ليس الأتمتة بشكل أعمى، ولكن تقليل وقت الكتابة لكل منتج، مع الحفاظ على التحكم التحريري.

في النهاية، ستعرف كيفية التنفيذ:

  • زر "إنشاء باستخدام الذكاء الاصطناعي" في محرر المنتج (لوحة تحكم WooCommerce).
  • واجهة برمجة تطبيقات الذكاء الاصطناعي التي يتم استدعاؤها عبر wp_remote_post() (بلا (SDK) مع مهلة زمنية ومعالجة الأخطاء.
  • ذاكرة تخزين مؤقتة واحدة لكل عملية عابرة لتجنب دفع ثمن نفس الجيل مرة أخرى.
  • التحديث الآمن لـ وصف مطول et وصف مختصر (مقتطف) من المنتج.
  • نظام بسيط لتحديد معدل الرسوم لتجنب الارتفاعات المفاجئة (والفواتير غير المتوقعة).

ملخص سريع

  • نضيف مربع بيانات تعريفية لـ WooCommerce مع زر يقوم بتشغيل طلب إدارة AJAX.
  • يقوم الخادم بإنشاء موجه من بيانات المنتج (السمات، الفئات، إلخ).
  • مكالمة ذكاء اصطناعي مع wp_remote_post()مهلة قصيرة، عدد محدود من المحاولات، أخطاء مسجلة.
  • استجابة نظيفة مع wp_kses_post() قبل إدخالها في post_content et post_excerpt.
  • قم بالتخزين المؤقت عبر واجهة برمجة تطبيقات Transients لتجنب إعادة الإنشاء إذا لم يتغير شيء.
  • مفتاح API المخزن في wp-config.php (لم يتم تضمينها في الكود الثابت، ولم يتم تضمينها في جانب جافا سكريبت).

متى يُستخدم الذكاء الاصطناعي لهذا الغرض؟

استخدم الذكاء الاصطناعي عندما يكون لديك حجم كبير من البيانات، وبنية بيانات موثوقة، وحاجة إلى الاتساق. عادةً:

  • متاجر متعددة البائعين حيث تصل السجلات في شكل بيانات خام (CSV، ERP) مع القليل من النصوص.
  • كتالوجات ذات اختلافات (الحجم/اللون) حيث تريد أوصافًا موحدة، ويتم إدارة الاختلافات بواسطة السمات.
  • "الأساس" لتحسين محركات البحث : إنتاج نص نظيف وسهل القراءة ذو بنية ثابتة (فقرات، قوائم)، ثم تحسين المنتجات الاستراتيجية.
  • تدويل (النسخة المتقدمة): إنشاء نسخة باللغات الإنجليزية/الألمانية/الإسبانية من قاعدة فرنسية، مع التحقق البشري.

في سيناريوهات العالم الحقيقي، يُفضّل إنشاء المنتج عند إنشائه، ثم إعادة إنشائه فقط في حال تغيير بعض الحقول (الخصائص، الفئة، العلامة التجارية). ويُسهم التخزين المؤقت القائم على "بصمة" المنتج بشكل كبير في ذلك.

متى لا يجب استخدام الذكاء الاصطناعي

تجنب استخدام الذكاء الاصطناعي إذا كان الحل التقليدي أكثر قوة أو أقل خطورة:

  • أوصاف قانونية بحتة (مستحضرات التجميل، المكملات الغذائية، المنتجات الطبية): خطر الهلوسة حقيقي. يُفضل استخدام قوالب PHP مع حقول/سمات ACF، أو قاعدة بيانات معتمدة قانونيًا.
  • منتجات ذات تقنية عالية حيث يؤدي أدنى خطأ إلى نتائج سلبية لخدمة العملاء (التوافق، المعايير). لا يزال بإمكانك استخدام الذكاء الاصطناعي، ولكن فقط على نافذة منبثقة مقفلة مع التحقق الإلزامي.
  • مواقع ذات قيود تكلفة صارمة للغاية إذا كان لديك 20000 منتج وتقوم بإعادة إنشائها بشكل متكرر، فإن فاتورة واجهة برمجة التطبيقات ترتفع بسرعة إذا لم تستخدم التخزين المؤقت ومعالجة الدفعات.
  • عندما تكون بيانات المصدر الخاصة بك سيئة (عناوين غير متناسقة، سمات فارغة). سيقوم الذكاء الاصطناعي "بالابتكار" لملء الفراغات. في هذه الحالة، ابدأ بتنظيف الكتالوج.

من الممارسات الخاطئة الشائعة تشغيل عملية إنشاء جديدة مع كل عملية حفظ تلقائية. ينتهي بك الأمر بعشرات استدعاءات واجهة برمجة التطبيقات لمنتج واحد لأن غوتنبرغ يحفظ تلقائيًا، و ووكومرس يُحدّث البيانات الوصفية، ويتم تشغيل خطافك بشكل متسلسل.

المتطلبات الأساسية

بحلول أبريل 2026، استهدف على الأقل ما يلي:

  • WordPress 6.9.4 (سياقك) و PHP 8.1 +.
  • WooCommerce مُحدَّث (الفرع 2026). يعتمد الكود أدناه على واجهات برمجة تطبيقات ووردبريس المستقرة، وليس على مكونات ووكومرس الداخلية الهشة.
  • الولوج إلى wp-config.php (أو متغيرات البيئة) لتخزين مفتاح API.

قم بتخزين مفتاح API (wp-config.php)

أضف هذا إلى wp-config.php (من الناحية المثالية، يتم ذلك من خلال إدارة كلمات المرور الخاصة بمزود الاستضافة، ولكن الثابت يظل أساسًا جيدًا).

/**
 * Clé API OpenAI (exemple).
 * Ne la commitez jamais dans Git. Ne la mettez jamais dans un plugin.
 */
define('BPCAB_OPENAI_API_KEY', 'sk-proj-...');

إذا كنت تفضل استخدام متغيرات البيئة، فيمكنك القيام بما يلي:

define('BPCAB_OPENAI_API_KEY', getenv('BPCAB_OPENAI_API_KEY') ?: '');

مراجع رسمية مفيدة

بنية الحل

التدفق (مخطط نصي):

إدارة المنتج (زر الإنشاء) ← إدارة AJAX (nonce + الإمكانيات) ← استرجاع بيانات المنتج ← حساب التجزئة ← ذاكرة تخزين مؤقتة؟ ← خلاف ذلك wp_remote_post() → واجهة برمجة تطبيقات الذكاء الاصطناعي → تحليل JSON → تنظيف البيانات (wp_kses_post()) ← تحديث المنتج (المحتوى + مقتطف) ← يتم إرجاع JSON إلى المسؤول

ما الذي يحدث وراء الكواليس؟

  • مسؤول واجهة المستخدم مربع بيانات يحتوي على زر بالإضافة إلى حقل "ملاحظات" اختياري. لا يحتوي ملف جافا سكريبت على مفتاح واجهة برمجة التطبيقات (API).
  • نقطة نهاية AJAX مؤمن بواسطة رقم عشوائي (nonce) + current_user_can('edit_product', $product_id).
  • موجه مبني من عناصر موثوقة (سمات ووكومرس، والفئات، والوسوم، والأسعار إذا كنت ترغب في ذلك).
  • مخبأ : مؤقت يعتمد على تجزئة البيانات. إذا لم يطرأ أي تغيير، فسيتم إرجاع الوصف الذي تم إنشاؤه مسبقًا.
  • التعقيم يمكن للذكاء الاصطناعي إرجاع كود HTML. ولا يُسمح إلا بما يسمح به ووردبريس في المنشور. wp_kses_post().

الكود الكامل - خطوة بخطوة

أنصحك بالذهاب إلى مو-المساعد لمنع تعطل قالب فرعي أو إضافة مقتطف برمجي أثناء التحديث، قم بإنشاء ما يلي: wp-content/mu-plugins/bpcab-ai-woo-descriptions.php.

1) احفظ مربع البيانات الوصفية في لوحة تحكم المنتج

نستخدم الشاشة productيظل هذا المربع التعريفي متوافقًا بغض النظر عن أداة إنشاء الواجهة الأمامية الخاصة بك (Divi/Elementor/Avada)، لأنه يتم تنفيذه على جانب إدارة WooCommerce.

<?php
/**
 * Plugin Name: BPCAB - IA descriptions produits WooCommerce
 * Description: Génère des descriptions produits via IA depuis l'admin WooCommerce (WP 6.9.4+, PHP 8.1+).
 * Version: 1.0.0
 */

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

add_action('add_meta_boxes', function () {
    add_meta_box(
        'bpcab_ai_product_desc',
        'Descriptions IA',
        'bpcab_render_ai_metabox',
        'product',
        'side',
        'high'
    );
});

function bpcab_render_ai_metabox(WP_Post $post): void {
    $product_id = (int) $post->ID;

    if (!current_user_can('edit_product', $product_id)) {
        echo '<p>Vous n’avez pas les droits pour modifier ce produit.</p>';
        return;
    }

    wp_nonce_field('bpcab_ai_generate_desc', 'bpcab_ai_nonce');

    echo '<p>
        <label for="bpcab_ai_notes"><strong>Notes internes (optionnel)</strong></label>
        <textarea id="bpcab_ai_notes" style="width:100%;min-height:70px;" placeholder="Ex: ton premium, mentionner la garantie 2 ans, éviter les superlatifs..."></textarea>
    </p>';

    echo '<p>
        <button type="button" class="button button-primary" id="bpcab-ai-generate" data-product-id="' . esc_attr((string) $product_id) . '">
            Générer avec IA
        </button>
    </p>';

    echo '<div id="bpcab-ai-status" style="margin-top:8px;"></div>';
}

2) قم بتحميل سكربت جافا سكريبت فقط على شاشة المنتج

من الأخطاء الشائعة التي ألاحظها هي وضع البرنامج النصي في كل مكان في لوحة التحكم. هذا يُبطئ الأمور بلا داعٍ وقد يُسبب تعارضات مع إضافات أخرى.

add_action('admin_enqueue_scripts', function (string $hook_suffix) {
    // Écrans typiques : post.php (édition), post-new.php (création)
    if (!in_array($hook_suffix, ['post.php', 'post-new.php'], true)) {
        return;
    }

    $screen = get_current_screen();
    if (!$screen || $screen->post_type !== 'product') {
        return;
    }

    wp_enqueue_script(
        'bpcab-ai-woo-admin',
        plugins_url('bpcab-ai-woo-admin.js', __FILE__),
        ['jquery'],
        '1.0.0',
        true
    );

    wp_localize_script('bpcab-ai-woo-admin', 'BPCAB_AI', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('bpcab_ai_generate_desc'),
    ]);
});

بعد ذلك، قم بإنشاء الملف wp-content/mu-plugins/bpcab-ai-woo-admin.jsنعم، يمكن لملحق mu تحميل ملف جافا سكريبت منفصل، طالما أن المسار صحيح. إذا كانت بنيتك التحتية تمنع ذلك، فضع ملف جافا سكريبت في ملحق عادي (فهذا غالبًا ما يكون أبسط).

(function ($) {
  function setStatus(html) {
    $('#bpcab-ai-status').html(html);
  }

  $(document).on('click', '#bpcab-ai-generate', function () {
    var productId = $(this).data('product-id');
    var notes = $('#bpcab_ai_notes').val() || '';

    setStatus('<p>Génération en cours… (ne fermez pas cet onglet)</p>');

    $.ajax({
      url: BPCAB_AI.ajaxUrl,
      method: 'POST',
      dataType: 'json',
      data: {
        action: 'bpcab_ai_generate_product_desc',
        nonce: BPCAB_AI.nonce,
        product_id: productId,
        notes: notes
      }
    })
    .done(function (resp) {
      if (!resp || !resp.success) {
        var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Erreur inconnue.';
        setStatus('<p style="color:#b32d2e;"><strong>Échec:</strong> ' + msg + '</p>');
        return;
      }

      setStatus(
        '<p style="color:#1e7e34;"><strong>OK:</strong> Descriptions mises à jour.</p>' +
        '<p>Astuce: cliquez sur “Mettre à jour” pour déclencher les hooks habituels de votre stack (cache, SEO, etc.).</p>'
      );
    })
    .fail(function (xhr) {
      setStatus('<p style="color:#b32d2e;"><strong>Erreur AJAX:</strong> vérifiez la console et vos logs PHP.</p>');
    });
  });
})(jQuery);

3) إنشاء نقطة نهاية AJAX (الأمان + التحقق)

نتحقق من قيمة nonce والأذونات ومعرّف المنتج. ونمنع المكالمات إذا لم يتم تكوين مفتاح API.

add_action('wp_ajax_bpcab_ai_generate_product_desc', function () {
    // Vérification nonce
    $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) $_POST['nonce']) : '';
    if (!wp_verify_nonce($nonce, 'bpcab_ai_generate_desc')) {
        wp_send_json_error(['message' => 'Nonce invalide. Rechargez la page produit.'], 403);
    }

    $product_id = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
    if ($product_id <= 0) {
        wp_send_json_error(['message' => 'ID produit invalide.'], 400);
    }

    if (!current_user_can('edit_product', $product_id)) {
        wp_send_json_error(['message' => 'Droits insuffisants.'], 403);
    }

    if (!defined('BPCAB_OPENAI_API_KEY') || !BPCAB_OPENAI_API_KEY) {
        wp_send_json_error(['message' => 'Clé API manquante. Définissez BPCAB_OPENAI_API_KEY dans wp-config.php.'], 500);
    }

    $notes = isset($_POST['notes']) ? wp_kses_post((string) $_POST['notes']) : '';

    $result = bpcab_ai_generate_and_update_product($product_id, $notes);

    if (is_wp_error($result)) {
        wp_send_json_error([
            'message' => $result->get_error_message(),
            'code'    => $result->get_error_code(),
        ], 500);
    }

    wp_send_json_success(['updated' => true]);
});

4) استرجع بيانات المنتج وقم بإنشاء الموجه

نعتمد على ووكومرس إن كان متوفرًا؛ وإلا، نعرض خطأً واضحًا. لا تخلط بين هذا وبين الخطاف. save_post إلى أن تستقر عملية التدفق: يكون AJAX أكثر قابلية للتنبؤ.

function bpcab_ai_generate_and_update_product(int $product_id, string $notes = '') {
    if (!function_exists('wc_get_product')) {
        return new WP_Error('woocommerce_missing', 'WooCommerce ne semble pas actif.');
    }

    $product = wc_get_product($product_id);
    if (!$product) {
        return new WP_Error('product_missing', 'Produit introuvable.');
    }

    // Rate limiting basique (par produit + utilisateur) pour éviter les rafales.
    $user_id = get_current_user_id();
    $rl_key = 'bpcab_ai_rl_' . $user_id . '_' . $product_id;
    if (get_transient($rl_key)) {
        return new WP_Error('rate_limited', 'Vous générez trop vite. Attendez 30 secondes et réessayez.');
    }
    set_transient($rl_key, 1, 30);

    $payload = bpcab_build_product_payload_for_prompt($product);

    // Empreinte: si les données n'ont pas changé, on peut servir depuis cache.
    $fingerprint = hash('sha256', wp_json_encode([
        'payload' => $payload,
        'notes'   => $notes,
        'v'       => '1.0.0', // incrémentez si vous changez la logique de prompt
    ]));

    $cache_key = 'bpcab_ai_desc_' . $product_id . '_' . substr($fingerprint, 0, 12);
    $cached = get_transient($cache_key);
    if (is_array($cached) && isset($cached['long'], $cached['short'])) {
        return bpcab_update_product_descriptions($product_id, $cached['long'], $cached['short']);
    }

    $prompt = bpcab_build_prompt($payload, $notes);

    $ai = bpcab_call_openai_responses_api($prompt);
    if (is_wp_error($ai)) {
        return $ai;
    }

    // Sanitation: on autorise un HTML "post" standard, pas de scripts, pas d'iframes.
    $long = wp_kses_post($ai['long'] ?? '');
    $short = wp_kses_post($ai['short'] ?? '');

    // Fallback si l'IA renvoie vide (ça arrive avec des prompts trop stricts).
    if (mb_strlen(wp_strip_all_tags($long)) < 80) {
        return new WP_Error('ai_empty', 'La réponse IA est trop courte ou vide. Vérifiez vos données produit et le prompt.');
    }
    if (mb_strlen(wp_strip_all_tags($short)) < 30) {
        $short = wp_trim_words(wp_strip_all_tags($long), 35, '…');
    }

    // Cache 30 jours (vous pouvez réduire si votre catalogue change souvent).
    set_transient($cache_key, ['long' => $long, 'short' => $short], 30 * DAY_IN_SECONDS);

    return bpcab_update_product_descriptions($product_id, $long, $short);
}

function bpcab_build_product_payload_for_prompt(WC_Product $product): array {
    $product_id = $product->get_id();

    $cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']);
    $tags = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'names']);

    // Attributs WooCommerce (pa_*) et attributs custom.
    $attributes_out = [];
    foreach ($product->get_attributes() as $attr) {
        if ($attr->is_taxonomy()) {
            $taxonomy = $attr->get_name();
            $label = wc_attribute_label($taxonomy);
            $terms = wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);
            $attributes_out[] = [
                'label' => $label,
                'values' => array_values(array_filter(array_map('sanitize_text_field', $terms))),
            ];
        } else {
            $attributes_out[] = [
                'label' => sanitize_text_field($attr->get_name()),
                'values' => array_values(array_filter(array_map('sanitize_text_field', $attr->get_options()))),
            ];
        }
    }

    $sku = (string) $product->get_sku();
    $price = $product->get_price();
    $regular = $product->get_regular_price();
    $sale = $product->get_sale_price();

    return [
        'title' => sanitize_text_field($product->get_name()),
        'sku' => sanitize_text_field($sku),
        'categories' => array_values(array_filter(array_map('sanitize_text_field', (array) $cats))),
        'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $tags))),
        'attributes' => $attributes_out,
        'price' => $price !== '' ? (string) $price : '',
        'regular_price' => $regular !== '' ? (string) $regular : '',
        'sale_price' => $sale !== '' ? (string) $sale : '',
        'short_description_existing' => wp_strip_all_tags((string) $product->get_short_description()),
        'description_existing' => wp_strip_all_tags((string) $product->get_description()),
    ];
}

function bpcab_build_prompt(array $payload, string $notes = ''): string {
    $attrs_lines = [];
    foreach (($payload['attributes'] ?? []) as $attr) {
        $label = $attr['label'] ?? '';
        $values = $attr['values'] ?? [];
        if (!$label || empty($values)) {
            continue;
        }
        $attrs_lines[] = '- ' . $label . ' : ' . implode(', ', $values);
    }

    $notes_clean = trim(wp_strip_all_tags($notes));

    // Prompt orienté e-commerce: bénéfices, usage, contraintes, pas de promesses non vérifiées.
    $prompt = "Vous êtes un rédacteur e-commerce senior. Rédigez pour WooCommerce une description produit en FR.nn";
    $prompt .= "Règles STRICTES:n";
    $prompt .= "1) N'inventez aucune caractéristique non fournie.n";
    $prompt .= "2) Pas de superlatifs gratuits ("le meilleur", "incroyable").n";
    $prompt .= "3) Style clair, concret, orienté bénéfices et usages.n";
    $prompt .= "4) HTML autorisé: <p>, <ul>, <li>, <strong>, <em>. Pas de titres H1/H2.n";
    $prompt .= "5) Retournez STRICTEMENT un JSON valide avec les clés: long_html, short_html.nn";

    $prompt .= "Données produit:n";
    $prompt .= "- Titre: " . ($payload['title'] ?? '') . "n";
    if (!empty($payload['sku'])) {
        $prompt .= "- SKU: " . $payload['sku'] . "n";
    }
    if (!empty($payload['categories'])) {
        $prompt .= "- Catégories: " . implode(', ', (array) $payload['categories']) . "n";
    }
    if (!empty($payload['tags'])) {
        $prompt .= "- Tags: " . implode(', ', (array) $payload['tags']) . "n";
    }
    if (!empty($attrs_lines)) {
        $prompt .= "- Attributs:n" . implode("n", $attrs_lines) . "n";
    }

    // Le prix est optionnel: utile si vous voulez positionnement "entrée de gamme/premium".
    if (!empty($payload['price'])) {
        $prompt .= "- Prix actuel (indicatif): " . $payload['price'] . "n";
    }

    if ($notes_clean !== '') {
        $prompt .= "nNotes internes:n" . $notes_clean . "n";
    }

    // Si une description existe déjà, on peut demander une amélioration plutôt qu'une réécriture totale.
    $existing = trim((string) ($payload['description_existing'] ?? ''));
    if ($existing !== '' && mb_strlen($existing) > 80) {
        $prompt .= "nTexte existant (à améliorer sans changer le sens):n" . $existing . "n";
    }

    $prompt .= "nFormat attendu (exemple):n";
    $prompt .= "{"long_html":"<p>...</p>","short_html":"<p>...</p>"}n";

    return $prompt;
}

5) استدعاء واجهة برمجة تطبيقات الذكاء الاصطناعي عبر wp_remote_post()

مثال باستخدام واجهة برمجة تطبيقات "الاستجابات" من OpenAI. إذا كنت تستخدم موفرًا آخر (Anthropic، Mistral، Google)، فإن البنية تتغير، لكن المبادئ تظل كما هي: مهلة زمنية، ومعالجة الأخطاء، والتحليل الدقيق.

أقوم بفرض استجابة JSON في موجه الأوامر، ثم أقوم بتحليلها من جانب الخادم. هذا يتجنب 80% من الاستجابات "المطولة" التي تعرقل عملية الإدخال.

function bpcab_call_openai_responses_api(string $prompt) {
    $endpoint = 'https://api.openai.com/v1/responses';

    $body = [
        // Modèle: choisissez un modèle "mini" pour réduire les coûts si la qualité vous suffit.
        // Adaptez selon votre compte et les modèles disponibles.
        'model' => 'gpt-4.1-mini',
        'input' => [
            [
                'role' => 'user',
                'content' => [
                    [
                        'type' => 'input_text',
                        'text' => $prompt,
                    ],
                ],
            ],
        ],
        // Limite raisonnable: descriptions produit, pas un roman.
        'max_output_tokens' => 700,
        'temperature' => 0.6,
    ];

    $args = [
        'timeout' => 25, // Évitez 60s: en admin, ça se ressent vite.
        'headers' => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
        ],
        'body' => wp_json_encode($body),
    ];

    $res = wp_remote_post($endpoint, $args);

    if (is_wp_error($res)) {
        return new WP_Error('http_error', 'Erreur HTTP vers l’API IA: ' . $res->get_error_message());
    }

    $code = (int) wp_remote_retrieve_response_code($res);
    $raw = (string) wp_remote_retrieve_body($res);

    if ($code < 200 || $code >= 300) {
        // Log minimal (évitez de logger des données sensibles).
        error_log('[BPCAB AI] API non-200: ' . $code . ' body=' . substr($raw, 0, 500));
        return new WP_Error('api_non_200', 'Réponse IA invalide (HTTP ' . $code . '). Vérifiez votre clé/quota.');
    }

    $json = json_decode($raw, true);
    if (!is_array($json)) {
        return new WP_Error('json_decode', 'Réponse IA non JSON (json_decode a échoué).');
    }

    // Récupération du texte: selon l’API, la sortie peut être structurée.
    // On essaie plusieurs chemins connus, puis on échoue proprement.
    $text = '';

    // Chemin courant: output_text agrégé
    if (isset($json['output_text']) && is_string($json['output_text'])) {
        $text = $json['output_text'];
    }

    // Fallback: certaines réponses contiennent output[] avec content[]
    if ($text === '' && !empty($json['output']) && is_array($json['output'])) {
        foreach ($json['output'] as $item) {
            if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
                continue;
            }
            foreach ($item['content'] as $c) {
                if (is_array($c) && ($c['type'] ?? '') === 'output_text' && isset($c['text'])) {
                    $text .= (string) $c['text'];
                }
            }
        }
    }

    $text = trim($text);
    if ($text === '') {
        error_log('[BPCAB AI] Sortie vide. Raw=' . substr($raw, 0, 500));
        return new WP_Error('empty_output', 'L’API IA a renvoyé une sortie vide.');
    }

    // On attend un JSON strict dans $text
    $out = json_decode($text, true);
    if (!is_array($out)) {
        error_log('[BPCAB AI] JSON attendu mais non parsable. Text=' . substr($text, 0, 500));
        return new WP_Error('bad_format', 'Format IA inattendu. Ajustez le prompt (JSON strict).');
    }

    $long = isset($out['long_html']) ? (string) $out['long_html'] : '';
    $short = isset($out['short_html']) ? (string) $out['short_html'] : '';

    return [
        'long' => $long,
        'short' => $short,
    ];
}

6) تحديث المنتج (المحتوى + المقتطف)

يتم تحديث منشور ووردبريس الأساسي. ثم يقرأ ووكومرس هذه الحقول لعرض الوصف. أنا أستخدم wp_update_post() للبقاء ضمن سير العمل القياسي لـ WordPress (الخطافات، والمراجعات إذا تم تمكينها، وما إلى ذلك).

function bpcab_update_product_descriptions(int $product_id, string $long_html, string $short_html) {
    $update = [
        'ID' => $product_id,
        'post_content' => $long_html,
        'post_excerpt' => $short_html,
    ];

    $res = wp_update_post(wp_slash($update), true, false);

    if (is_wp_error($res)) {
        return new WP_Error('update_failed', 'Échec mise à jour produit: ' . $res->get_error_message());
    }

    // Optionnel: forcer une mise à jour du produit WooCommerce (index, lookup tables, etc.)
    if (function_exists('wc_get_product')) {
        $product = wc_get_product($product_id);
        if ($product) {
            $product->save();
        }
    }

    return true;
}

الكود المجمع بالكامل

انسخ هذا الملف والصقه في wp-content/mu-plugins/bpcab-ai-woo-descriptions.phpقم أيضًا بإنشاء جافا سكريبت bpcab-ai-woo-admin.js بجانبه (الموضح أعلاه).

<?php
/**
 * Plugin Name: BPCAB - IA descriptions produits WooCommerce
 * Description: Génère des descriptions produits via IA depuis l'admin WooCommerce (WP 6.9.4+, PHP 8.1+).
 * Version: 1.0.0
 */

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

/**
 * Meta box sur l'écran produit.
 */
add_action('add_meta_boxes', function () {
    add_meta_box(
        'bpcab_ai_product_desc',
        'Descriptions IA',
        'bpcab_render_ai_metabox',
        'product',
        'side',
        'high'
    );
});

function bpcab_render_ai_metabox(WP_Post $post): void {
    $product_id = (int) $post->ID;

    if (!current_user_can('edit_product', $product_id)) {
        echo '<p>Vous n’avez pas les droits pour modifier ce produit.</p>';
        return;
    }

    wp_nonce_field('bpcab_ai_generate_desc', 'bpcab_ai_nonce');

    echo '<p>
        <label for="bpcab_ai_notes"><strong>Notes internes (optionnel)</strong></label>
        <textarea id="bpcab_ai_notes" style="width:100%;min-height:70px;" placeholder="Ex: ton premium, mentionner la garantie 2 ans, éviter les superlatifs..."></textarea>
    </p>';

    echo '<p>
        <button type="button" class="button button-primary" id="bpcab-ai-generate" data-product-id="' . esc_attr((string) $product_id) . '">
            Générer avec IA
        </button>
    </p>';

    echo '<div id="bpcab-ai-status" style="margin-top:8px;"></div>';
}

/**
 * JS admin (uniquement sur l'écran produit).
 */
add_action('admin_enqueue_scripts', function (string $hook_suffix) {
    if (!in_array($hook_suffix, ['post.php', 'post-new.php'], true)) {
        return;
    }

    $screen = get_current_screen();
    if (!$screen || $screen->post_type !== 'product') {
        return;
    }

    wp_enqueue_script(
        'bpcab-ai-woo-admin',
        plugins_url('bpcab-ai-woo-admin.js', __FILE__),
        ['jquery'],
        '1.0.0',
        true
    );

    wp_localize_script('bpcab-ai-woo-admin', 'BPCAB_AI', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('bpcab_ai_generate_desc'),
    ]);
});

/**
 * Endpoint AJAX sécurisé.
 */
add_action('wp_ajax_bpcab_ai_generate_product_desc', function () {
    $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) $_POST['nonce']) : '';
    if (!wp_verify_nonce($nonce, 'bpcab_ai_generate_desc')) {
        wp_send_json_error(['message' => 'Nonce invalide. Rechargez la page produit.'], 403);
    }

    $product_id = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
    if ($product_id <= 0) {
        wp_send_json_error(['message' => 'ID produit invalide.'], 400);
    }

    if (!current_user_can('edit_product', $product_id)) {
        wp_send_json_error(['message' => 'Droits insuffisants.'], 403);
    }

    if (!defined('BPCAB_OPENAI_API_KEY') || !BPCAB_OPENAI_API_KEY) {
        wp_send_json_error(['message' => 'Clé API manquante. Définissez BPCAB_OPENAI_API_KEY dans wp-config.php.'], 500);
    }

    $notes = isset($_POST['notes']) ? wp_kses_post((string) $_POST['notes']) : '';

    $result = bpcab_ai_generate_and_update_product($product_id, $notes);

    if (is_wp_error($result)) {
        wp_send_json_error([
            'message' => $result->get_error_message(),
            'code'    => $result->get_error_code(),
        ], 500);
    }

    wp_send_json_success(['updated' => true]);
});

function bpcab_ai_generate_and_update_product(int $product_id, string $notes = '') {
    if (!function_exists('wc_get_product')) {
        return new WP_Error('woocommerce_missing', 'WooCommerce ne semble pas actif.');
    }

    $product = wc_get_product($product_id);
    if (!$product) {
        return new WP_Error('product_missing', 'Produit introuvable.');
    }

    $user_id = get_current_user_id();
    $rl_key = 'bpcab_ai_rl_' . $user_id . '_' . $product_id;
    if (get_transient($rl_key)) {
        return new WP_Error('rate_limited', 'Vous générez trop vite. Attendez 30 secondes et réessayez.');
    }
    set_transient($rl_key, 1, 30);

    $payload = bpcab_build_product_payload_for_prompt($product);

    $fingerprint = hash('sha256', wp_json_encode([
        'payload' => $payload,
        'notes'   => $notes,
        'v'       => '1.0.0',
    ]));

    $cache_key = 'bpcab_ai_desc_' . $product_id . '_' . substr($fingerprint, 0, 12);
    $cached = get_transient($cache_key);
    if (is_array($cached) && isset($cached['long'], $cached['short'])) {
        return bpcab_update_product_descriptions($product_id, $cached['long'], $cached['short']);
    }

    $prompt = bpcab_build_prompt($payload, $notes);

    $ai = bpcab_call_openai_responses_api($prompt);
    if (is_wp_error($ai)) {
        return $ai;
    }

    $long = wp_kses_post($ai['long'] ?? '');
    $short = wp_kses_post($ai['short'] ?? '');

    if (mb_strlen(wp_strip_all_tags($long)) < 80) {
        return new WP_Error('ai_empty', 'La réponse IA est trop courte ou vide. Vérifiez vos données produit et le prompt.');
    }
    if (mb_strlen(wp_strip_all_tags($short)) < 30) {
        $short = wp_trim_words(wp_strip_all_tags($long), 35, '…');
    }

    set_transient($cache_key, ['long' => $long, 'short' => $short], 30 * DAY_IN_SECONDS);

    return bpcab_update_product_descriptions($product_id, $long, $short);
}

function bpcab_build_product_payload_for_prompt(WC_Product $product): array {
    $product_id = $product->get_id();

    $cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']);
    $tags = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'names']);

    $attributes_out = [];
    foreach ($product->get_attributes() as $attr) {
        if ($attr->is_taxonomy()) {
            $taxonomy = $attr->get_name();
            $label = wc_attribute_label($taxonomy);
            $terms = wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);
            $attributes_out[] = [
                'label' => $label,
                'values' => array_values(array_filter(array_map('sanitize_text_field', $terms))),
            ];
        } else {
            $attributes_out[] = [
                'label' => sanitize_text_field($attr->get_name()),
                'values' => array_values(array_filter(array_map('sanitize_text_field', $attr->get_options()))),
            ];
        }
    }

    $sku = (string) $product->get_sku();
    $price = $product->get_price();
    $regular = $product->get_regular_price();
    $sale = $product->get_sale_price();

    return [
        'title' => sanitize_text_field($product->get_name()),
        'sku' => sanitize_text_field($sku),
        'categories' => array_values(array_filter(array_map('sanitize_text_field', (array) $cats))),
        'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $tags))),
        'attributes' => $attributes_out,
        'price' => $price !== '' ? (string) $price : '',
        'regular_price' => $regular !== '' ? (string) $regular : '',
        'sale_price' => $sale !== '' ? (string) $sale : '',
        'short_description_existing' => wp_strip_all_tags((string) $product->get_short_description()),
        'description_existing' => wp_strip_all_tags((string) $product->get_description()),
    ];
}

function bpcab_build_prompt(array $payload, string $notes = ''): string {
    $attrs_lines = [];
    foreach (($payload['attributes'] ?? []) as $attr) {
        $label = $attr['label'] ?? '';
        $values = $attr['values'] ?? [];
        if (!$label || empty($values)) {
            continue;
        }
        $attrs_lines[] = '- ' . $label . ' : ' . implode(', ', $values);
    }

    $notes_clean = trim(wp_strip_all_tags($notes));

    $prompt = "Vous êtes un rédacteur e-commerce senior. Rédigez pour WooCommerce une description produit en FR.nn";
    $prompt .= "Règles STRICTES:n";
    $prompt .= "1) N'inventez aucune caractéristique non fournie.n";
    $prompt .= "2) Pas de superlatifs gratuits ("le meilleur", "incroyable").n";
    $prompt .= "3) Style clair, concret, orienté bénéfices et usages.n";
    $prompt .= "4) HTML autorisé: <p>, <ul>, <li>, <strong>, <em>. Pas de titres H1/H2.n";
    $prompt .= "5) Retournez STRICTEMENT un JSON valide avec les clés: long_html, short_html.nn";

    $prompt .= "Données produit:n";
    $prompt .= "- Titre: " . ($payload['title'] ?? '') . "n";
    if (!empty($payload['sku'])) {
        $prompt .= "- SKU: " . $payload['sku'] . "n";
    }
    if (!empty($payload['categories'])) {
        $prompt .= "- Catégories: " . implode(', ', (array) $payload['categories']) . "n";
    }
    if (!empty($payload['tags'])) {
        $prompt .= "- Tags: " . implode(', ', (array) $payload['tags']) . "n";
    }
    if (!empty($attrs_lines)) {
        $prompt .= "- Attributs:n" . implode("n", $attrs_lines) . "n";
    }
    if (!empty($payload['price'])) {
        $prompt .= "- Prix actuel (indicatif): " . $payload['price'] . "n";
    }

    if ($notes_clean !== '') {
        $prompt .= "nNotes internes:n" . $notes_clean . "n";
    }

    $existing = trim((string) ($payload['description_existing'] ?? ''));
    if ($existing !== '' && mb_strlen($existing) > 80) {
        $prompt .= "nTexte existant (à améliorer sans changer le sens):n" . $existing . "n";
    }

    $prompt .= "nFormat attendu (exemple):n";
    $prompt .= "{"long_html":"<p>...</p>","short_html":"<p>...</p>"}n";

    return $prompt;
}

function bpcab_call_openai_responses_api(string $prompt) {
    $endpoint = 'https://api.openai.com/v1/responses';

    $body = [
        'model' => 'gpt-4.1-mini',
        'input' => [
            [
                'role' => 'user',
                'content' => [
                    [
                        'type' => 'input_text',
                        'text' => $prompt,
                    ],
                ],
            ],
        ],
        'max_output_tokens' => 700,
        'temperature' => 0.6,
    ];

    $args = [
        'timeout' => 25,
        'headers' => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
        ],
        'body' => wp_json_encode($body),
    ];

    $res = wp_remote_post($endpoint, $args);

    if (is_wp_error($res)) {
        return new WP_Error('http_error', 'Erreur HTTP vers l’API IA: ' . $res->get_error_message());
    }

    $code = (int) wp_remote_retrieve_response_code($res);
    $raw = (string) wp_remote_retrieve_body($res);

    if ($code < 200 || $code >= 300) {
        error_log('[BPCAB AI] API non-200: ' . $code . ' body=' . substr($raw, 0, 500));
        return new WP_Error('api_non_200', 'Réponse IA invalide (HTTP ' . $code . '). Vérifiez votre clé/quota.');
    }

    $json = json_decode($raw, true);
    if (!is_array($json)) {
        return new WP_Error('json_decode', 'Réponse IA non JSON (json_decode a échoué).');
    }

    $text = '';
    if (isset($json['output_text']) && is_string($json['output_text'])) {
        $text = $json['output_text'];
    }

    if ($text === '' && !empty($json['output']) && is_array($json['output'])) {
        foreach ($json['output'] as $item) {
            if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
                continue;
            }
            foreach ($item['content'] as $c) {
                if (is_array($c) && ($c['type'] ?? '') === 'output_text' && isset($c['text'])) {
                    $text .= (string) $c['text'];
                }
            }
        }
    }

    $text = trim($text);
    if ($text === '') {
        error_log('[BPCAB AI] Sortie vide. Raw=' . substr($raw, 0, 500));
        return new WP_Error('empty_output', 'L’API IA a renvoyé une sortie vide.');
    }

    $out = json_decode($text, true);
    if (!is_array($out)) {
        error_log('[BPCAB AI] JSON attendu mais non parsable. Text=' . substr($text, 0, 500));
        return new WP_Error('bad_format', 'Format IA inattendu. Ajustez le prompt (JSON strict).');
    }

    return [
        'long' => isset($out['long_html']) ? (string) $out['long_html'] : '',
        'short' => isset($out['short_html']) ? (string) $out['short_html'] : '',
    ];
}

function bpcab_update_product_descriptions(int $product_id, string $long_html, string $short_html) {
    $update = [
        'ID' => $product_id,
        'post_content' => $long_html,
        'post_excerpt' => $short_html,
    ];

    $res = wp_update_post(wp_slash($update), true, false);

    if (is_wp_error($res)) {
        return new WP_Error('update_failed', 'Échec mise à jour produit: ' . $res->get_error_message());
    }

    if (function_exists('wc_get_product')) {
        $product = wc_get_product($product_id);
        if ($product) {
            $product->save();
        }
    }

    return true;
}

شرح الكود

لماذا زر (AJAX) بدلاً من خطاف تلقائي؟

في ووكومرس، يُمكن حفظ المنتج عدة مرات دون الحاجة إلى النقر على "تحديث": الحفظ التلقائي، والمراجعات، وتحديثات البيانات الوصفية من إضافات أخرى، ومزامنة المخزون، وما إلى ذلك. إذا قمت بتفعيل الذكاء الاصطناعي على save_post_product بدون إجراءات وقائية، ستؤدي إلى إطلاق استدعاءات وهمية لواجهة برمجة التطبيقات (API).

يُجبر الزر على اتخاذ إجراء مُتعمّد. وهذا يُسهّل عملية تصحيح الأخطاء: تنقر، ترى الحالة، تُصلحها.

لماذا يتم استخدام بيانات مؤقتة تعتمد على التجزئة؟

ذاكرة تخزين مؤقت بسيطة "لكل منتج" (على سبيل المثال: bpcab_ai_desc_123هذا مضللٌ بسرعة، لأن المنتج يتغير. يضمن تجزئة SHA-256 للحمولة + الملاحظات ما يلي: نفس المدخلات ← نفس المخرجات ← لا سداد.

حالة نادرة: إذا قمت بتعديل موجه الأوامر (بنيته، قواعده)، فستحتاج إلى إبطال صلاحية ذاكرة التخزين المؤقت. ومن هنا جاء الحقل الصغير. v في الهاش.

لماذا نستخدم wp_kses_post() وليس sanitize_text_field()؟

sanitize_text_field() يؤدي ذلك إلى إتلاف بنية HTML. مع ذلك، يحتاج وصف WooCommerce إلى قوائم ونصوص غامقة وفقرات. عمليًا، wp_kses_post() الحل الوسط الصحيح هو: السماح باستخدام لغة HTML في منشور واحد، وإزالة البرامج النصية.

ما زلت أرى مواقع تقوم بإدراج استجابة الذكاء الاصطناعي "الخام" في post_contentإذا أعاد الذكاء الاصطناعي رابطًا مشبوهًا أو رمز HTML غير مألوف، فقد فتحت للتو ثغرة أمنية غير ضرورية. هذا ليس هجوم XSS "تلقائيًا" (يقوم ووردبريس بالفعل بتصفية بعض المواقع)، لكنك بذلك تُعقّد الأمور على نفسك.

لماذا فترة توقف قصيرة؟

في لوحة التحكم، يؤدي ضبط مهلة 60 ثانية إلى تعطيل واجهة المستخدم، مما يجعل ووردبريس يبدو وكأنه متجمد. يُنصح بترك مهلة قصوى تتراوح بين 20 و30 ثانية. إذا كانت لديك منتجات معقدة للغاية، ففكر في استخدام التوليد غير المتزامن (cron/queue)، أو استكشف الخيارات المتقدمة.

تكاليف واجهة برمجة التطبيقات وتحسينها

تعتمد التكلفة على النموذج وحجم الرموز المميزة. يمكن أن تكلف صفحة المنتج النموذجية (الرسالة + الاستجابة) تقريبًا، من 800 إلى 2000 رمز يعتمد ذلك على عدد السمات والطول المطلوب. مع النموذج "المصغر"، غالبًا ما تكون تكلفة كل عملية إنشاء منخفضة جدًا، ولكن هذا الأمر مهم على نطاق واسع.

تقدير بسيط (يتم تعديله ليناسب نموذجك)

  • لنفترض أن لدينا 1200 رمز لكل منتج (مدخلات + مخرجات).
  • 1000 منتج/شهر ← 1,2 مليون رمز/شهر.
  • أما بالنسبة للطراز "الصغير"، فعادةً ما يكون سعره معقولاً، ولكن إذا قمت بإعادة الإنشاء (بدون التخزين المؤقت)، يمكنك الضرب بسرعة في 3 أو 10.

التحسينات التي تعمل بالفعل

  • ذاكرة التخزين المؤقت ببصمة الإصبع (موجود بالفعل): إنه أفضل عائد على الاستثمار.
  • قلل من حجم الإخراج مع max_output_tokens : يتجنب الإجابات المطولة.
  • نموذج أصغر أما بالنسبة للكميات الكبيرة، فإن النموذج الأقوى مخصص فقط للمنتجات المتميزة.
  • دفعة (النسخة المتقدمة): معالجة 50 منتجًا في مهمة cron الليلية، بميزانية يومية.
  • قلل من التنبيه لا ترسل سمات فارغة، ولا الوصف الحالي إذا كان فارغًا.

المتغيرات المتقدمة وحالات الاستخدام

الخيار الأول: إنشاء الوصف المختصر فقط (مقتطف)

مفيد عندما يكون وصفك الطويل مكتوبًا بالفعل (أو مقدمًا من قبل الشركة المصنعة)، ولكنك تريد مقتطفًا مناسبًا للمبيعات ومتسقًا لصفحات الفئات.

  • عدّل الرسالة بحيث تطلب فقط short_html.
  • في bpcab_update_product_descriptions()التحديث فقط post_excerpt.

الخيار الثاني: التوليد غير المتزامن (تجنب حالات انتهاء المهلة)

على خوادم الاستضافة البطيئة، قد تستغرق المكالمة الخارجية أكثر من 25 ثانية. في هذه الحالة، قم بتشغيل مهمة مؤجلة.

  • AJAX: يسجل "طلبًا" (بيانات ما بعد النشر) ويستجيب على الفور.
  • تقوم مهمة cron (WP-Cron أو خادم cron) بمعالجة الطلبات في قائمة انتظار، بمعدل 5 طلبات في الدقيقة.

لن أضع كل الكود هنا حرصاً على سهولة نسخه، ولكن النقطة الأساسية هي أن لا تقم بإجراء مكالمة الذكاء الاصطناعي في طلب المسؤول إذا كانت بنيتك التحتية غير مستقرة.

الخيار الثالث: التوافق مع Divi 5 / Elementor / Avada

تتم عملية الإنشاء على جانب WooCommerce، لذا فهي تبقى متوافقة. والأمر المثير للاهتمام هو عرض "شارة" أو قسم "أبرز الميزات" على صفحة المنتج عبر أداة إنشاء الموقع.

  • ديفي 5 يمكنك إنشاء وحدة تقرأ post_excerpt أو حقل "نقاط القوة" المُنشأ بواسطة الذكاء الاصطناعي. إذا بقيتَ post_content/post_excerptليس لدى ديفي أي شيء مميز لتفعله.
  • Elementor استخدم أداة "وصف المنتج المختصر" وسيتم عرضها تلقائيا المقتطف المحدث.
  • فتح : يعكس مكون WooCommerce "محتوى المنتج" / "مقتطف المنتج" هذه الحقول دون تعديل.

نصيحة أستخدمها كثيراً: اطلب من الذكاء الاصطناعي قائمة <ul> أضف المزايا في الوصف المطوّل، ثمّ نسّق هذه القائمة باستخدام القالب/أداة الإنشاء. ستحصل بذلك على تناسق بصري دون الحاجة إلى أي برمجة إضافية.

السلامة وأفضل الممارسات

لا تكشف مفتاح API من جانب العميل

مسؤول جافا سكريبت يتصل فقط admin-ajax.phpالمفتاح لا يزال في wp-config.phpإذا قمت بوضع المفتاح في نص برمجي، حتى في لوحة التحكم، فسوف يتم نسخه في مكان ما (ذاكرة التخزين المؤقت، ملحقات المتصفح، الخادم الوكيل).

التحقق من الصحة والحد من القيود

  • القدرات : edit_product الحد الأدنى. لا تدع دور "مدير المتجر" غير الخاضع للرقابة يتسبب في 10000 جيل.
  • مناسبة حالية : موجودة بالفعل لمنع طلبات CSRF.
  • الحد من معدل مدة الزيارة المؤقتة 30 ثانية لكل مستخدم + منتج. ولزيادة المدة، أضف حصة يومية (عداد الزيارات المؤقتة).

قم بتطهير استجابة الذكاء الاصطناعي

wp_kses_post() هذا هو الحد الأدنى. إذا كنت ترغب في أن تكون أكثر صرامة، يمكنك تمرير قائمة بالعلامات المسموح بها إلى wp_kses() ورفض أي روابط خارجية.

اللائحة العامة لحماية البيانات / البيانات المرسلة

لا تُرسل بيانات شخصية (اسم العميل، عنوانه، إلخ). هنا، نُرسل بيانات المنتج فقط. إذا كنت تخطط لتخصيص أوصاف المنتج للمستخدم، فأنت بذلك تدخل في سياق أكثر حساسية يتعلق باللائحة العامة لحماية البيانات (الأساس القانوني، المقاول من الباطن، قانون حماية البيانات، إلخ).

أخطاء واقعية يجب تجنبها

  • نسخ الكود إلى المكان الخطأ مقتطف مُلصق في functions.php سيتم فقدان القالب الرئيسي في التحديث القادم.
  • انسَ الفاصلة المنقوطة خطأ واحد في PHP ← شاشة بيضاء في لوحة التحكم. اختبر على بيئة تجريبية.
  • خطاف غير لائق : تشغيل عند init ou save_post بدون ضمانات ← مكالمات الذكاء الاصطناعي غير المقصودة.
  • اختبار في بيئة الإنتاج بدون نسخة احتياطية يمكنك استبدال الأوصاف الموجودة بكميات كبيرة.
  • لغة PHP قديمة جدًا بعض قواعد الكتابة/الأنواع المذكورة أعلاه تفترض استخدام PHP 8.1 أو أحدث. أما في الإصدار 7.4، فإنها لا تعمل.

كيفية الاختبار والتصحيح

1) قم بتمكين التسجيل بشكل صحيح

في wp-config.php (عند بدء التشغيل)، قم بالتفعيل:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

ثم ستقرأ wp-content/debug.logالوثائق الرسمية: تصحيح في وورد.

2) اختبر أولاً على منتج "بسيط"

  • عنوان واضح
  • تم ملء 2-3 سمات
  • فئة أ

إذا نجح الأمر، فانتقل إلى منتج متغير ذي سمات متعددة. تزيد المطالبات الطويلة جدًا من خطر الحصول على مخرجات غير متوافقة (أو مبتورة).

3) تحقق من طلب AJAX

  • افتح متصفح DevTools → الشبكة → admin-ajax.php.
  • انظر إلى رمز HTTP (200/403/500) و JSON المُعاد.

4) تحقق من استجابة واجهة برمجة التطبيقات (API)

إذا واجهت أخطاء في تنسيق JSON، فقم بتسجيل مقتطفات فقط (تم ذلك بالفعل باستخدام substr()لا تقم بتسجيل كامل الرسالة إذا كنت تضع معلومات حساسة فيها.

إذا لم ينجح الأمر

إليكم مخطط تشخيصي أستخدمه فعلاً عند استكشاف أخطاء هذا النوع من التكامل وإصلاحها.

عرض السبب المحتمل التحقق الحلول
زر "إنشاء" لا يفعل شيئًا. لم يتم تحميل جافا سكريبت على شاشة المنتج وحدة تحكم أدوات المطورين/الشبكة، غياب bpcab-ai-woo-admin.js الاختيار admin_enqueue_scriptsالمسار plugins_url()وأن ملف JS موجود
خطأ 403 "قيمة عشوائية غير صالحة" لم يتم إرسال الإشعار أو الصفحة قديمة جدًا الشبكة ← حمولة POST تحتوي على nonce ? أعد تحميل صفحة المنتج، ثم تحقق. wp_localize_script وفعل المبعوث البابوي
خطأ 500 "مفتاح API مفقود" غائب ثابت / فارغ الاختيار wp-config.php والبيئة حدد BPCAB_OPENAI_API_KEYقم بمسح ذاكرة التخزين المؤقت للعمليات إذا لزم الأمر
خطأ HTTP 401/403 من جانب واجهة برمجة تطبيقات الذكاء الاصطناعي مفتاح غير صالح، مشروع غير مصرح به السجلات: "رمز خطأ API غير 200" + رمز الخطأ أعد إنشاء المفتاح، وتحقق من أذونات المشروع من جانب الموفر.
HTTP 429 تجاوزت الحصة / حد سعر المورد سجلات + لوحة معلومات المورد أضف وظيفة التراجع/إعادة المحاولة، وقلل سرعة الساعة، وقم بتمكين التخزين المؤقت، واستخدم نموذجًا أخف وزنًا.
"صيغة ذكاء اصطناعي غير متوقعة" يُعيد الذكاء الاصطناعي نصًا غير بتنسيق JSON سجل جزئي للمخرجات اجعل التوجيهات أكثر صرامة، وأقل صرامة. temperatureقلل الطول المطلوب
تم سحق الأوصاف "بشكل عشوائي" اختبار على بيئة الإنتاج + نقرات متعددة + حفظ تلقائي سجل التعديلات / السجلات العمل على مرحلة التجهيز، إضافة قفل أطول (مؤقت)، طلب تأكيد.

مأزقان شائعان

  • تم تعطيل المقتطف بواسطة إضافة المقتطفات إذا كنت تستخدم إضافة من نوع "مقتطفات برمجية"، فقد يؤدي خطأ في التحليل إلى تعطيل المقتطف، مما يجعل واجهة الإدارة غير مستقرة. تُعدّ إضافة mu-plugin أكثر استقرارًا لهذا النوع من التعليمات البرمجية.
  • صراع خفي تعتمد بعض المواقع على ذاكرة تخزين مؤقتة للكائنات الدائمة بشكل مكثف. قد يتم مشاركة البيانات المؤقتة بين البيئات في حال وجود خلل في الإعدادات. إذا لاحظتَ أوصافًا غير متطابقة، فابدأ بتعطيل ذاكرة التخزين المؤقتة للكائنات مؤقتًا في بيئة الاختبار.

الموارد

الأسئلة الشائعة

هل يعمل مع المنتجات المتغيرة (الاختلافات)؟

نعم، لكن الكود أعلاه يُنشئ وصفًا لـ المنتج الأصليإذا كنت ترغب في إضافة نص لكل صيغة، فعليك المرور على الصيغ المختلفة وتخزينها في علامات تعريفية مخصصة (وتعديل طريقة العرض). نادرًا ما أفعل ذلك: فهو مكلف وغير ضروري في كثير من الأحيان من منظور تحسين محركات البحث.

هل يمكنني منع الذكاء الاصطناعي من ذكر السعر؟

نعم: قم بإزالة الحقل price من الحمولة والسطر المقابل في الموجه. أنصح بعدم تضمين الأسعار إذا كنت تُجري عروضًا ترويجية بشكل متكرر، وإلا سيصبح النص قديمًا.

لماذا نطلب مخرجات JSON بدلاً من HTML بسيط؟

لأنك تريد حقلين (طويل + قصير) وتحليلاً قوياً. يقلل JSON من الحالات التي يضيف فيها الذكاء الاصطناعي جملًا خارج التنسيق. وعندما يحدث خطأ، ستراه فورًا (خطأ "تنسيق سيئ").

هل يمكنني استخدام أنثروبيك أو ميسترال أو جوجل بدلاً من ذلك؟

نعم. حافظ على نفس البنية (إدارة AJAX → wp_remote_post() → تحليل → wp_kses_post() → تحديث). يتغير فقط نقطة النهاية وتنسيق JSON. إذا زودتني بالمزود المحدد، فسأتمكن من تزويدك بوظيفة. bpcab_call_* ما يعادلها.

هل هناك خطر من أن يؤدي هذا إلى إنشاء محتوى مكرر؟

إذا كانت منتجاتك متشابهة جدًا (نفس الخصائص، عناوين متطابقة تقريبًا)، فقد ينتج الذكاء الاصطناعي نصوصًا متشابهة. للحد من ذلك: أضف شرط "التمييز حسب الاستخدام" إلى النص المطلوب، وأضف عنصرًا فريدًا (العلامة التجارية، المادة، الفائدة الرئيسية). ولكن مع الكتالوجات المتجانسة، لا مفر من وجود بعض التشابه.

كيف يمكننا منع الذكاء الاصطناعي من ابتكار خصائص جديدة؟

لن تتجنبه تمامًا. يمكنك تقليل المخاطر بشكل كبير عن طريق:

  • حظر الاختراع صراحة (تم ذلك بالفعل).
  • خفض temperature (
  • توفير البيانات المنظمة فقط (السمات) وتجنب "الملاحظات" الغامضة.

أواجه خطأ "تنسيق الذكاء الاصطناعي غير متوقع". ما الذي يجب عليّ فعله أولاً؟

تقليل التعقيد: عدد أقل من السمات، max_output_tokens في الأعلى (إذا تم اقتطاعها)، وبتعليمات أكثر صرامة ("أرجع JSON فقط، بدون نص قبله أو بعده"). في تجربتي، في تسع حالات من أصل عشر، يكون الناتج مجرد بيانات غير JSON.

لماذا يظهر زر "موافق" بينما لا أستطيع رؤية أي شيء في الواجهة؟

في كثير من الأحيان، يكون السبب هو ذاكرة التخزين المؤقت للصفحة (أو ذاكرة التخزين المؤقت للكائنات) التي تعرض إصدارًا قديمًا. امسح ذاكرة التخزين المؤقت وتأكد من عرض القالب بشكل صحيح. the_content() بالإضافة إلى مقتطف WooCommerce القياسي. أما مع Elementor/Avada/Divi، فتأكد من عدم عرض حقل مخصص بدلاً من ذلك.

هل يمكنني معاينة الوصف قبل استبداله؟

نعم: بدلاً من الاتصال wp_update_post()أعد إرسالها long_html et short_html في استجابة AJAX، اعرضها في نافذة منبثقة، ثم أضف زر "تطبيق" ثانياً. هذه هي النسخة التي أستخدمها في المتاجر التي تسمح بتعديلات متعددة.

هل يمكن أن يؤدي توليد عدد كبير منها إلى الإضرار بتحسين محركات البحث الخاص بي؟

إذا نشرت 500 ورقة بيانات منتج مُولّدة آليًا دون تدقيق لغوي، فأنت تُعرّض جودة المنتج للخطر (وبالتالي تُعرّض تحسين محركات البحث لموقعك للخطر). تتمثل آلية العمل السليمة في: توليد البيانات بواسطة الذكاء الاصطناعي ← التحقق البشري ← النشر. أما بالنسبة للمنتجات الاستراتيجية، فيتم إعادة كتابتها بالكامل.