إذا سبق لك أن أضفت "زرًا صغيرًا" إلى صفحة ما WordPressثم، ينتهي بك الأمر بـ 200 كيلوبايت من جافا سكريبت، والاعتماد على إطار عمل وذاكرة تخزين مؤقتة لا تفرغ أبدًا، عندها تكون قد واجهت المشكلة الحقيقية: التفاعل من جانب العميل يأتي بسرعة على حساب التعقيد.
منذ عدة إصدارات، يقدم ووردبريس نهجًا أكثر توافقًا مع النظام الأساسي: واجهة برمجة تطبيقات التفاعلية (Interactivity API). الفكرة بسيطة نظريًا: تحديد سلوكيات جانب العميل مباشرةً في ترميزك (عبر data-wp-*), مع متجر متجاوب تتم إدارته بواسطة ووردبريس، بدون React/Vue/Svelte في قالبك.
المشكلة / الحاجة
حاجة محددة: إنشاء مكون واجهة أمامية (مثل "إعجاب"، "عداد"، "تصفية"، "قائمة منسدلة"، "نسخ إلى الحافظة").تحميل "تقدمي" تفاعلي دون تضمين إطار عمل، مع الحفاظ على التوافق مع نظام WordPress البيئي (ذاكرة التخزين المؤقت، والقوالب، وقوالب الكتل، ومنشئي الصفحات، والأمان).
في النهاية، ستعرف:
- قم بإنشاء إضافة صغيرة تعرض كتلة (أو عرض HTML قابل للاستخدام في كل مكان) باستخدام واجهة برمجة تطبيقات التفاعلية.
- إدارة حالة العميل (المخزن) والإجراءات (المعالجات) بدون تبعيات خارجية.
- أضف نقطة نهاية AJAX/REST آمن للحفاظ على الحالة (على سبيل المثال، مثل العداد).
- تصحيح الأخطاء في حالات العالم الحقيقي: خلل في عملية الإضافة إلى قائمة الانتظار، والتخزين المؤقت المفرط، واختيار الخطافات بشكل سيئ، وتعارضات البناء.
ملخص سريع
- نقوم ببرمجة إضافة "زر الإعجاب" (عداد + تبديل) استنادًا إلى واجهة برمجة تطبيقات التفاعل، وهي متوافقة مع WordPress 6.9.4+ و PHP 8.1+.
- يستخدم الترميز
data-wp-interactive,data-wp-on--click,data-wp-text,data-wp-class--*. - يقوم متجر جافا سكريبت بإدارة الحالة المحلية (واجهة المستخدم المتفائلة)، ثم يقوم بالمزامنة عبر واجهة برمجة تطبيقات REST.
- من جانب PHP: نقطة نهاية REST
/wp-json/bpcab/v1/like، REST nonce، الأذونات، التنظيف/الهروب. - نقدم نسخة مختصرة من الكود بالإضافة إلى تكاملات Divi 5 / Elementor / أفادا.
متى يستخدم هذا الحل
- أنت تريد تفاعلية خفيفة الوزن (تبديل، عداد، حالة واجهة المستخدم) بدون حزمة React.
- أنت تقوم بتوفير قالب أو إضافة مخصصة لمواقع مختلفة (أدوات البناء، التخزين المؤقت، شبكة توصيل المحتوى) وتريد تقليل مخاطر تعارضات جافا سكريبت.
- أنت بحاجة إلى عرض SSR (HTML تم إنشاؤه بواسطة PHP) يظل قابلاً للاستخدام بدون JS، ثم "يتحسن" مع JS (تحسين تدريجي).
- تريد أن تلتزم بأنماط ووردبريس (enqueue، REST، nonce) وتتجنب "mini-SPA" في زاوية.
متى لا يجب استخدام هذا الحل
- أنت بصدد بناء تطبيق ويب متكامل ذي صفحة واحدة (SPA) يتضمن توجيهًا ونماذج معقدة وتقارير عالمية ضخمة. سيكون استخدام إطار عمل (أو على الأقل بنية أكثر متانة) أنسب.
- يجب عليك دعم المتصفحات القديمة جدًا (تتبع واجهة برمجة تطبيقات التفاعل المعايير الحديثة؛ تحقق من قيودك).
- يتم توفير التفاعلية لديك بشكل صحيح من خلال إضافة (مثل كتل WooCommerce، أو الجوانب المخصصة). إعادة اختراع العجلة مكلفة من حيث الصيانة.
- لديك متطلبات فورية (WebSocket، حالة التواجد، التعاون). واجهة برمجة تطبيقات التفاعل ليست بديلاً عن طبقة الوقت الفعلي.
المتطلبات الأساسية / قبل البدء
- الحد الأدنى المطلوب هو WordPress 6.9.4 (أبريل 2026)، وPHP 8.1+.
- بيئة اختبار (محلية أو تجريبية). تجنب اختبار هذا النوع من التعليمات البرمجية مباشرةً في بيئة الإنتاج: لقد رأيت نقطة نهاية REST ضعيفة الحماية تتعرض لهجوم مكثف في غضون دقائق.
- الوصول إلى الروابط الدائمة (الإعدادات) للتحقق من واجهة برمجة تطبيقات REST وإعادة إنشائها إذا لزم الأمر.
- أدوات مفيدة:
- مراقبة الاستعلام لفحص الخطافات/REST/perfs.
- متصفح أدوات المطورين (الشبكة + وحدة التحكم).
مصادر رسمية يُنصح بالاحتفاظ بها:
- واجهة برمجة تطبيقات التفاعل (دليل محرر الكتل)
- دليل واجهة برمجة تطبيقات REST
- register_rest_route()
- wp_enqueue_script ()
- تصفية/التحقق من صحة البيانات في PHP (php.net)
لأغراض المراقبة الأساسية: عند مواجهة سلوك غير معتاد (خاصةً عند التفاعل مع ذاكرة التخزين المؤقت أو المحرر)، تحقق من التذاكر على core.trac.wordpress.org والمسؤولون عن العلاقات العامة github.com/WordPress/gutenberg.
النهج الساذج (ولماذا يجب تجنبه)
النسخة التي ما زلت أراها كثيراً: زر عليه onclick مضمنة، أ fetch إلى admin-ajax.php، بدون رمز عشوائي، وبرنامج نصي محمل في كل مكان على الموقع.
<?php
// Exemple à NE PAS copier : inline JS, pas de nonce, pas de contrôle de permission.
echo '<button onclick="likePost(' . get_the_ID() . ')">Like</button>';
?>
<script>
function likePost(postId){
fetch('/wp-admin/admin-ajax.php?action=like&post_id=' + postId)
.then(r => r.json())
.then(console.log);
}
</script>
ما يحدث خلف الكواليس:
- الأمان: نقطة نهاية بدون قيمة عشوائية (nonce) = ثغرة أمنية بسيطة من نوع CSRF. وإذا لم تقم بالتحقق من صحة البيانات، فهذا يعني وجود ثغرة أمنية من نوع CSRF.
post_idأنت بذلك تفتح الباب أمام كتابات عشوائية. - الأداء: يتم حقن البرامج النصية في كل مكان، ومن المستحيل التحكم بها بدقة، ويصعب تخزينها مؤقتًا بشكل صحيح.
- الصيانة: في اليوم الذي تحتاج فيه إلى جعل المكون قابلاً للوصول (ARIA) أو قابلاً للاختبار أو متوافقًا مع أداة البناء، تبدأ من الصفر.
- DX: تصبح المعالجات المضمنة غير قابلة للإدارة بمجرد وجود نوعين من المكونات.
النهج الصحيح - دليل خطوة بخطوة
سنقوم ببناء إضافة بسيطة ونظيفة مع مكون "إعجاب" يعمل بشكل صحيح:
- بدون استخدام جافا سكريبت: يتم عرض الزر (ويمكنك تحديد خيار بديل).
- باستخدام جافا سكريبت: تبديل فوري + عداد محدّث، ثم مزامنة REST.
الخطوة 1 - إنشاء الملحق
أنشئ مجلدًا wp-content/plugins/bpcab-interactivity-like/ مع:
bpcab-interactivity-like.phpassets/like.js
الخطوة 2 - تحديد عرض HTML "التفاعلي"
تعتمد واجهة برمجة تطبيقات التفاعل على السمات data-wp-* في لغة الترميز. والأهم من ذلك: data-wp-interactive والذي "يربط" مساحة اسم المتجر بشجرة فرعية من DOM.
سنقوم بإنشاء زر وعداد. الزر:
- يؤدي إلى تنفيذ إجراء عبر
data-wp-on--click - يعرض نصًا ديناميكيًا عبر
data-wp-text - تبديل فئة CSS عبر
data-wp-class--is-liked
الخطوة 3 - تحميل البرنامج النصي بشكل صحيح (الإضافة المستهدفة)
المأزق الكلاسيكي: تحميل جافا سكريبت في كل صفحة. هنا، نقوم بتحميل السكربت فقط إذا كان المحتوى يحتوي على الكود المختصر (أو إذا تم عرض الكتلة الخاصة بنا). ولتبسيط الأمور وضمان موثوقيتها، سنقوم بما يلي:
- قم بتوفير رمز مختصر
[bpcab_like] - الكشف عن وجوده عبر
has_shortcode()في اللحظة المناسبة
ملاحظة: في المواقع التي تعتمد بشكل كبير على أدوات جاهزة (مثل Elementor/Divi)، قد يتم إنشاء المحتوى بطريقة مختلفة. سأقدم لكم بعض الاختلافات أدناه.
الخطوة 4 - كشف نقطة نهاية REST آمنة
نتجنب admin-ajax.php في هذه الحالة، تُعدّ واجهة برمجة تطبيقات REST أكثر سلاسةً، وأسهل استخدامًا للتخزين المؤقت، وأفضل تجهيزًا (كود HTTP، وJSON، والمصادقة). نقوم بإنشاء مسار:
POST /wp-json/bpcab/v1/likeمعpost_idetdelta(+1 أو -1)
نحن نحمي من خلال:
- REST Nonce (
wp_create_nonce( 'wp_rest' )) - التحقق الصارم من صحة المعايير
- الأذونات: يُسمح هنا للزوار المجهولين، لكن تأثيرهم محدود (تغيير بسيط، منشور موجود، نوع عام). وللحفاظ على بيانات كل مستخدم على حدة، يلزم نموذج هوية (معرّف المستخدم، أو ملف تعريف ارتباط موقّع، أو تخزين أكثر صرامة على الخادم).
الخطوة 5 - تفاعل المتجر (واجهة مستخدم متفائلة + التراجع)
النمط الذي أستخدمه غالباً:
- سنقوم بتحديث واجهة المستخدم فوراً (متفائلين).
- نرسل طلب REST
- إذا فشل ذلك، فإننا نتراجع عن الحالة ونسجلها بشكل صحيح.
الرمز الكامل
الملف 1: إضافة PHP
Créez wp-content/plugins/bpcab-interactivity-like/bpcab-interactivity-like.php.
<?php
/**
* Plugin Name: BPCAB Interactivity Like
* Description: Exemple avancé Interactivity API : bouton Like sans framework, avec synchronisation REST.
* Version: 1.0.0
* Requires at least: 6.9
* Requires PHP: 8.1
* Author: BPCAB
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
final class BPCAB_Interactivity_Like_Plugin {
private const SCRIPT_HANDLE = 'bpcab-like-interactivity';
private const REST_NAMESPACE = 'bpcab/v1';
private const REST_ROUTE = '/like';
public function hooks(): void {
add_action( 'init', [ $this, 'register_shortcode' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'maybe_enqueue_assets' ] );
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
}
public function register_shortcode(): void {
add_shortcode( 'bpcab_like', [ $this, 'render_shortcode' ] );
}
/**
* Rend le composant Like.
* Utilisable dans un article, une page, un widget texte, ou via do_shortcode().
*/
public function render_shortcode( array $atts = [] ): string {
$atts = shortcode_atts(
[
'post_id' => 0,
'label_like' => 'J’aime',
'label_unlike' => 'Je n’aime plus',
],
$atts,
'bpcab_like'
);
$post_id = (int) $atts['post_id'];
if ( $post_id <= 0 ) {
$post_id = get_the_ID() ? (int) get_the_ID() : 0;
}
if ( $post_id <= 0 ) {
return '';
}
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return '';
}
// Compteur stocké en post meta. Pour un site à fort trafic, préférez une table dédiée ou un agrégat asynchrone.
$likes = (int) get_post_meta( $post_id, '_bpcab_likes', true );
if ( $likes < 0 ) {
$likes = 0;
}
// État initial "liked" : ici, on ne persiste pas par utilisateur.
// On part sur false. Pour une vraie UX, vous pouvez lire un cookie signé ou une table user/post.
$initial_liked = false;
// Données initiales injectées dans le DOM (JSON) pour hydrater le store côté client.
$initial_state = [
'postId' => $post_id,
'likes' => $likes,
'liked' => $initial_liked,
'labels' => [
'like' => (string) $atts['label_like'],
'unlike' => (string) $atts['label_unlike'],
],
];
// Escaping : on encode en JSON, puis on échappe pour attribut HTML.
$initial_state_json = wp_json_encode( $initial_state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
if ( false === $initial_state_json ) {
return '';
}
// Markup Interactivity API.
// data-wp-interactive="bpcab/like" : namespace du store côté client.
// data-wp-context : contexte initial (state) accessible via le store.
$html = '<div class="bpcab-like"';
$html .= ' data-wp-interactive="bpcab/like"';
$html .= ' data-wp-context="' . esc_attr( $initial_state_json ) . '"';
$html .= '>';
$html .= '<button type="button" class="bpcab-like__button"';
$html .= ' data-wp-on--click="actions.toggle"';
$html .= ' data-wp-class--is-liked="state.liked"';
$html .= '>';
$html .= '<span class="bpcab-like__label" data-wp-text="state.liked ? state.labels.unlike : state.labels.like"></span>';
$html .= '</button>';
$html .= '<span class="bpcab-like__count" aria-live="polite">';
$html .= '<strong data-wp-text="state.likes">' . esc_html( (string) $likes ) . '</strong>';
$html .= '</span>';
$html .= '</div>';
return $html;
}
/**
* Enqueue conditionnel : on charge le JS seulement si le shortcode est présent.
* Attention : sur certaines pages builder, le contenu n'est pas dans post_content.
*/
public function maybe_enqueue_assets(): void {
if ( is_admin() ) {
return;
}
$post = get_post();
if ( ! $post ) {
return;
}
// Détection simple : shortcode présent dans post_content.
// Variante builder plus bas.
if ( ! has_shortcode( (string) $post->post_content, 'bpcab_like' ) ) {
return;
}
$asset_url = plugin_dir_url( __FILE__ ) . 'assets/like.js';
$asset_path = plugin_dir_path( __FILE__ ) . 'assets/like.js';
$ver = file_exists( $asset_path ) ? (string) filemtime( $asset_path ) : '1.0.0';
// Dépendances : le runtime Interactivity est fourni par WordPress (paquet @wordpress/interactivity).
// Le handle exact peut évoluer selon les versions ; en pratique WP expose les scripts nécessaires
// quand vous utilisez l'Interactivity API dans les blocs.
//
// Ici, on reste robuste : pas de dépendance forcée, mais on s'appuie sur le fait que WP charge
// le runtime interactivity quand le markup data-wp-interactive est présent.
wp_enqueue_script(
self::SCRIPT_HANDLE,
$asset_url,
[],
$ver,
[
'in_footer' => true,
'strategy' => 'defer',
]
);
// Données globales minimales : endpoint REST + nonce.
// Le nonce 'wp_rest' est le standard pour REST API.
wp_add_inline_script(
self::SCRIPT_HANDLE,
'window.BPCAB_LIKE = ' . wp_json_encode(
[
'restUrl' => esc_url_raw( rest_url( self::REST_NAMESPACE . self::REST_ROUTE ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
],
JSON_UNESCAPED_SLASHES
) . ';',
'before'
);
// Un peu de CSS minimal via inline (vous pouvez le sortir dans un fichier).
$css = '.bpcab-like{display:flex;gap:.6rem;align-items:center}.bpcab-like__button.is-liked{font-weight:700}.bpcab-like__count{opacity:.85}';
wp_register_style( 'bpcab-like-inline', false, [], $ver );
wp_enqueue_style( 'bpcab-like-inline' );
wp_add_inline_style( 'bpcab-like-inline', $css );
}
public function register_rest_routes(): void {
register_rest_route(
self::REST_NAMESPACE,
self::REST_ROUTE,
[
'methods' => 'POST',
'callback' => [ $this, 'rest_like' ],
'permission_callback' => [ $this, 'rest_permission' ],
'args' => [
'post_id' => [
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
'validate_callback' => function ( $value ) {
return is_numeric( $value ) && (int) $value > 0;
},
],
'delta' => [
'type' => 'integer',
'required' => true,
'sanitize_callback' => function ( $value ) {
$value = (int) $value;
if ( 1 === $value ) {
return 1;
}
if ( -1 === $value ) {
return -1;
}
return 0;
},
'validate_callback' => function ( $value ) {
$value = (int) $value;
return 1 === $value || -1 === $value;
},
],
],
]
);
}
/**
* Permission : on accepte les visiteurs anonymes, mais on exige un nonce REST valide.
* Si vous voulez limiter aux utilisateurs connectés : return is_user_logged_in();
*/
public function rest_permission( WP_REST_Request $request ): bool|WP_Error {
$nonce = $request->get_header( 'X-WP-Nonce' );
if ( ! $nonce ) {
return new WP_Error( 'bpcab_like_missing_nonce', 'Nonce manquant.', [ 'status' => 403 ] );
}
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
return new WP_Error( 'bpcab_like_invalid_nonce', 'Nonce invalide.', [ 'status' => 403 ] );
}
return true;
}
public function rest_like( WP_REST_Request $request ): WP_REST_Response|WP_Error {
$post_id = (int) $request->get_param( 'post_id' );
$delta = (int) $request->get_param( 'delta' );
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return new WP_Error( 'bpcab_like_invalid_post', 'Article introuvable.', [ 'status' => 404 ] );
}
// Optionnel : limiter aux post types publics.
$post_type_obj = get_post_type_object( (string) $post->post_type );
if ( ! $post_type_obj || ! $post_type_obj->public ) {
return new WP_Error( 'bpcab_like_forbidden_type', 'Type de contenu non autorisé.', [ 'status' => 403 ] );
}
// Mise à jour robuste.
// Note : update_post_meta n'est pas atomique. Sur très fort trafic, vous pouvez avoir des collisions.
// Pour un compteur critique, utilisez une table custom + requête SQL atomique, ou un système de queue.
$current = (int) get_post_meta( $post_id, '_bpcab_likes', true );
$new = $current + $delta;
if ( $new < 0 ) {
$new = 0;
}
update_post_meta( $post_id, '_bpcab_likes', $new );
return new WP_REST_Response(
[
'postId' => $post_id,
'likes' => $new,
],
200
);
}
}
add_action(
'plugins_loaded',
static function (): void {
( new BPCAB_Interactivity_Like_Plugin() )->hooks();
}
);
الملف 2: نص التفاعل
Créez wp-content/plugins/bpcab-interactivity-like/assets/like.js.
/* global BPCAB_LIKE */
(function () {
'use strict';
/**
* Interactivity API :
* On enregistre un store "bpcab/like" qui expose state/actions.
* Le state initial provient de data-wp-context (injecté côté PHP).
*
* Référence : https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/
*/
const { store, getContext } = window.wp && window.wp.interactivity ? window.wp.interactivity : {};
if (!store || !getContext) {
// Cas réel : le runtime interactivity n'est pas chargé (mauvais enqueue, optimisation agressive, plugin cache).
// On évite de casser la page.
return;
}
async function postLike({ postId, delta }) {
const res = await fetch(BPCAB_LIKE.restUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': BPCAB_LIKE.nonce
},
body: JSON.stringify({ post_id: postId, delta })
});
if (!res.ok) {
const text = await res.text().catch(() => '');
const err = new Error('REST error ' + res.status + ' ' + res.statusText);
err.details = text;
throw err;
}
return await res.json();
}
store('bpcab/like', {
state: {
get postId() {
return getContext().postId;
},
get likes() {
return getContext().likes;
},
get liked() {
return getContext().liked;
},
get labels() {
return getContext().labels || { like: 'J’aime', unlike: 'Je n’aime plus' };
}
},
actions: {
/**
* Toggle optimiste :
* - on inverse liked
* - on ajuste likes (+1/-1)
* - on synchronise REST
* - rollback en cas d'erreur
*/
async toggle() {
const ctx = getContext();
const previousLiked = !!ctx.liked;
const previousLikes = Number.isFinite(ctx.likes) ? ctx.likes : 0;
const nextLiked = !previousLiked;
const delta = nextLiked ? 1 : -1;
// Mise à jour optimiste.
ctx.liked = nextLiked;
ctx.likes = Math.max(0, previousLikes + delta);
try {
const data = await postLike({ postId: ctx.postId, delta });
// On resynchronise avec la vérité serveur.
if (data && typeof data.likes === 'number') {
ctx.likes = data.likes;
}
} catch (e) {
// Rollback.
ctx.liked = previousLiked;
ctx.likes = previousLikes;
// Log discret. En prod, vous pouvez envoyer vers un logger.
// Ne faites pas d'alert() agressif.
console.error('[bpcab/like] Échec synchronisation', e);
}
}
}
});
})();
شرح الكود
المنطق العام (البسيط)
يقوم PHP بإنشاء ملف HTML "عادي" (زر + عداد) ويضيف ما يلي:
data-wp-interactive="bpcab/like"لإخبار ووردبريس: "هذا الفرع يستخدم متجر bpcab/like".data-wp-context="...json..."لإدخال الحالة الأولية (postId، الإعجابات، المعجب به، التصنيفات).data-wp-on--click="actions.toggle"لربط النقر بإجراء جافا سكريبت.data-wp-text="..."لعرض قيمة محسوبة من الحالة.
يخزن ملف جافا سكريبت مخزنًا باسم "bpcab/like". عند نقر المستخدم، يتم تعديل السياق (تفاعلي)، ثم يتم استدعاء واجهة برمجة تطبيقات REST لحفظ العداد.
التفاصيل الفنية (الخطافات، والأمان، وتكامل ووردبريس 6.9.4)
add_shortcode()يُعدّ هذا عمليًا لمثال قابل للنقل. أما في بيئة الإنتاج، فستستخدم غالبًا كتلة ديناميكية (render_callback) للتحكم بشكل أدق داخل المحرر.wp_enqueue_scriptsنحن نجري تحقيقاً من الخطوط الأمامية. النقطة الحساسة هي اكتشاف الوجود. إذا لم يقم برنامج البناء الخاص بك بوضع الرمز المختصر فيpost_contentلن يتم تحميل البرنامج النصيسأتناول الاختلافات أدناه.- REST API :
register_rest_route()يكشف الطريق.permission_callbackتحقق من قيمة nonceX-WP-Nonce(CSRF).- التحقق الصارم من
deltaضمن نطاق ±1. بدون ذلك، سترى الناس ينشرونdelta=999999بمجرد أن تصبح نقطة النهاية عامة.
- شروط السباق عداد ما بعد البيانات الوصفية ليس عملية ذرية. في ظل حركة مرور عالية، قد يؤدي استعلامان متزامنان إلى استبدال قيمة. هذا مقبول بالنسبة لمدونة عادية؛ أما بالنسبة لمنصة إعلامية ذات حجم كبير، فأستخدم جدولًا مخصصًا واستعلامات ذرية (أو مخزنًا مؤقتًا/قائمة انتظار).
Pourquoi data-wp-context بدلا من wp_localize_script على سبيل المثال
wp_localize_script يتم استخدام المعرّف (عبر المعرّف)، وهو ما يصبح معقدًا عند وجود 10 مكونات في نفس الصفحة. هنا، يحمل كل عنصر ملف JSON خاص به. وهذا أكثر قابلية للتوسع عند وجود حلقة من المقالات (على سبيل المثال، صفحة فئة تحتوي على 20 "إعجابًا").
المتغيرات وحالات الاستخدام
الخيار 1 - أزرار متعددة على صفحة واحدة (حلقة) بدون استخدام جافا سكريبت عامة معقدة
يمكنك وضع [bpcab_like post_id="123"] في حلقة تكرارية، أو قم بإنشاء HTML باستخدام PHP. كل مكون له خصائصه الخاصة. data-wp-contextالمتجر مشترك، لكن السياق خاص بكل حالة: هذه هي النقطة بالضبط.
الخيار الثاني - تخزين عدد الإعجابات لكل مستخدم (مسجل الدخول) بدلاً من عداد بسيط
النمط الذي أستخدمه:
- إذا كان المستخدم مسجلاً دخوله: قم بتخزين قائمة (أو جدول علائقي) بالمنشورات التي أعجب بها المستخدم في بيانات المستخدم.
- عمليات التحقق من نقطة نهاية REST
is_user_logged_in()و يستخدمget_current_user_id(). - يبقى عداد التجميع في بيانات المنشور (أو الجدول)، لكن حالة "الإعجاب" تصبح موثوقة.
تنبيه: قد يزداد حجم قائمة بيانات المستخدمين. في المواقع الكبيرة، يُنصح باستخدام جدول علائقي مخصص (معرّف المستخدم، معرّف المنشور، تاريخ الإنشاء) بالإضافة إلى فهرس.
الخيار 3 - وضع "غير مستمر" (واجهة المستخدم فقط) للأكورديونات/علامات التبويب
إذا كانت حاجتك تقتصر على واجهة المستخدم فقط (مثل الأكورديون وعلامات التبويب)، فقم بإزالة نقطة نهاية REST واحتفظ فقط بما يلي:
data-wp-on--clickلتغيير قيمة منطقية في السياقdata-wp-class--is-open/data-wp-textليعكس الحالة
في تجربتي، هذه هي أفضل نقطة دخول: لا يوجد نظام خلفي، ولا يوجد أمان، ويمكنك التحقق من صحة مسار التوافق بين قائمة الانتظار/ذاكرة التخزين المؤقت.
التوافق مع Divi 5 / Elementor / Avada
النقطة التي تتعطل في أغلب الأحيان: فشل تحميل البرنامج النصي لأن المحتوى النهائي الخاص بك لا يحتوي على الرمز المختصر في post_content.
ديفي 5
يمكن لـ Divi تخزين التخطيطات في علامات التعريف الوصفية، ولا يمكن دائمًا اكتشاف العرض النهائي عبر has_shortcode( $post->post_content )خياران قويان:
- استعلام "كبير" فقط على الصفحات التي تعرف أن الوحدة النمطية تظهر فيها (على سبيل المثال، عبر قالب مشروط).
- أو اكتشاف وجود الترميز الذي تم عرضه في وقت العرض (المخزن المؤقت) - وهو أكثر هشاشة.
أحد الأساليب العملية التي استخدمتها كثيراً: إضافة إعداد "فرض الإضافة" صغير عبر مرشح.
<?php
// À mettre dans votre plugin (ou un mu-plugin) si Divi ne déclenche pas has_shortcode().
add_filter( 'bpcab_like_force_enqueue', function( bool $force ): bool {
if ( function_exists( 'et_theme_builder_get_template_layouts' ) ) {
// Indice que Divi est actif. À affiner selon votre contexte.
return true;
}
return $force;
}, 10, 1 );
ثم قم بالتكيف maybe_enqueue_assets() لتطبيق هذا الفلتر (إذا كنت ترغب في هذا الخيار، فافعل ذلك بشكل صحيح). لم أقم بتضمينه افتراضيًا للحفاظ على بساطة الإضافة.
Elementor
في Elementor، غالبًا ما يتم عرض المحتوى من خلال الوسوم الوصفية. إليك حلان أنيقان:
- قم بإنشاء عنصر واجهة مستخدم Elementor مخصص يقوم بعرض الترميز (ويسجل تبعية البرنامج النصي عبر واجهة برمجة تطبيقات Elementor).
- أو الاستعلام عن صفحات Elementor عبر شرط (الكشف عن المكونات الإضافية + البيانات الوصفية).
إذا اخترت الكشف البسيط:
<?php
// Exemple : détecter une page construite avec Elementor.
$is_elementor = (bool) get_post_meta( get_the_ID(), '_elementor_edit_mode', true );
أفادا (منشئ الاندماج)
تمتلك Avada رموزًا مختصرة خاصة بها ونظام عرض خاص بها. الطريقة الأكثر موثوقية: استخدام الرمز المختصر [bpcab_like] مباشرة في كتلة/عنصر "الرمز" أو "الرمز المختصر".
إذا كان برنامج Avada يقوم بتصغير/دمج الملفات بشكل مفرط، فتحقق مما يلي:
- لم يتم نقل النص البرمجي إلى
headبلاdefer - لا يتم "إزالة" وقت تشغيل التفاعلية عن طريق التحسين
فحوصات ما بعد التثبيت
- في صفحة تحتوي على
[bpcab_like]افتح أدوات المطورين > الشبكة:- تأكد من ذلك
assets/like.jsمشحونة بالكامل (الحالة 200) - تأكد من ذلك
POST /wp-json/bpcab/v1/likeيُعيد رمز 200 عند النقر
- تأكد من ذلك
- أدوات المطورين > وحدة التحكم:
- لا يوجد خطأ
window.wp.interactivityغير محدد
- لا يوجد خطأ
- من جانب ووردبريس:
- الميتا
_bpcab_likesيقوم بتحديث نفسه (يمكنك التحقق من ذلك عبر WP-CLI أو إضافة meta).
- الميتا
مخطط التشخيص السريع
| عرض | السبب المحتمل | التحقق | الحلول |
|---|---|---|---|
| يظهر الزر ولكنه لا يستجيب. | لم يتم تحميل التفاعل أثناء التشغيل أو لم يتم تحميل جافا سكريبت | وحدة التحكم: window.wp.interactivity هل هو موجود؟ الشبكة: like.js تكلفة ؟ |
لتصحيح قم بتفعيل قائمة الانتظار، وتعطيل تحسين جافا سكريبت، وتحقق من أداة الإنشاء. |
| خطأ 403 في مسار REST | قيمة عشوائية مفقودة/غير صالحة | الشبكة: رأس الصفحة X-WP-Nonce هنا ؟ |
متحقق wp_add_inline_scriptذاكرة تخزين مؤقتة لـ HTML، وشبكة توصيل محتوى (CDN) تزيل الرؤوس |
| يقفز العداد إلى الخلف | التراجع بعد خطأ REST | وحدة التحكم: سجل [bpcab/like] Échec synchronisation |
تحقق من عناوين URL الخاصة بـ REST، والروابط الدائمة، وجدار حماية تطبيقات الويب (WAF)، و CORS، ومكونات الأمان الإضافية. |
| يكون العداد غير مستقر تحت الحمل | حالة سباق على بيانات التعريف المنشورة | قم بمحاكاة النقرات المتزامنة (k6/ab) وقارنها | جدول مخصص + تحديث ذري، أو تجميع غير متزامن |
إذا لم ينجح الأمر
- تحقق من مكان لصق الكود يجب أن يكون هذا الملحق موجودًا في
wp-content/plugins/...وتم تفعيله. لقد رأيت بالفعل ملف PHP مُلصقًا في القالب، ثم "يعمل على صفحة واحدة ولا يعمل على الأخرى". - تحقق من PHP في إصدارات PHP الأقدم من 8.1، قد تواجه أخطاء في أنواع البيانات. راجع
wp-content/debug.logsiWP_DEBUGانه مفعل. - تحقق من الروابط الدائمة إذا أعاد REST API خطأ 404، فانتقل إلى الإعدادات > الروابط الدائمة واحفظ التغييرات (دون تغيير أي شيء). هذه مشكلة شائعة أثناء عمليات النقل.
- تعطيل تحسين جافا سكريبت مؤقتًا (cache/minify): لقد واجهت في كثير من الأحيان مشكلة "تعطل" التفاعل أثناء التشغيل بسبب عملية دمج تؤدي إلى تغيير ترتيب التحميل.
- اختبر نقطة نهاية REST باستخدام curl :
curl -i -X POST "https://example.com/wp-json/bpcab/v1/like"
-H "Content-Type: application/json"
-H "X-WP-Nonce: VOTRE_NONCE"
--data '{"post_id":123,"delta":1}'
إذا لم يكن لديك قيمة عشوائية (nonce) في متناول يدك، فعلى الأقل اختبر وجود المسار (يجب أن يعيد 403 "Nonce مفقود"، وهي علامة جيدة).
الأخطاء الشائعة والمزالق
| خطأ | سبب | الحلول |
|---|---|---|
Uncaught TypeError: Cannot destructure property 'store' ... |
window.wp.interactivity غير موجود (لم يتم تحميل وقت التشغيل) |
تحقق من قائمة الانتظار، وقم بتعطيل خاصية التصغير، وتأكد من أن ووردبريس يقوم بتحميل نصوص التفاعل. |
| لا يتم تحميل البرنامج النصي أبدًا | has_shortcode() لا يكتشف (المنشئ، يتم عرضه عبر البيانات الوصفية) |
أضف أداة إنشاء الشروط (Elementor/Divi/Avada) أو قم بإضافتها إلى قائمة الانتظار عبر أداة/وحدة مخصصة |
403 Nonce invalide |
تم تخزين HTML مؤقتًا باستخدام رمز عشوائي منتهي الصلاحية، أو تم عرض الصفحة عبر ذاكرة التخزين المؤقت للصفحة الكاملة | استبعد الصفحة من ذاكرة التخزين المؤقت، أو قم بإنشاء قيمة nonce عبر نقطة نهاية عامة، أو قم بتقييدها للمستخدمين المسجلين. |
SyntaxError: Unexpected token في data-wp-context |
تم ترميز/تهريب JSON بشكل غير صحيح، وعلامات اقتباس مكسورة | دائماً اتبع الخطوات wp_json_encode ثم esc_attr |
| خطأ فادح بعد النسخ واللصق | قوس مفقود/فاصلة منقوطة، أو تم وضع الملف في المجلد الخطأ | تحقق من صحة البيانات باستخدام أداة تدقيق الأخطاء، وقم بتمكين وضع تصحيح الأخطاء (WP_DEBUG) في بيئة الاختبار، وصحح بناء الجملة. |
| يصبح العداد سالباً | لم يتم التحقق من صحة دلتا أو النقر المزدوج السريع | التحقق من صحة delta ±1، مع تثبيت القيمة عند 0 على كل من جانب الخادم وجانب العميل |
| تعارض مع إضافة أمان/جدار حماية تطبيقات الويب | حظر طلبات أو رؤوس REST POST | أضف المسار إلى القائمة البيضاء /wp-json/bpcab/v1/likeتحقق من سجلات جدار حماية تطبيقات الويب (WAF) |
| أنت تستخدم مقتطفًا قديمًا تم العثور عليه في عام 2023 | تغيرت واجهة برمجة التطبيقات/المقابض، وأصبحت الممارسات قديمة. | يستهدف هذا الإصدار ووردبريس 6.9.4 والإصدارات الأحدث، استنادًا إلى وثائق التفاعلية الحالية. |
نصائح السلامة والأداء والصيانة
أمن
- REST nonce مطلوب إذا قام جهازك بتعديل البيانات، فبدون قيمة عشوائية (nonce)، يُعتبر ذلك هجوم تزوير الطلبات عبر المواقع (CSRF).
- تأكد من الإعدادات : هنا
deltaهو ±1 بدقة. إنه حاجز بسيط ولكنه فعال. - فكر في الإساءة يمكن التلاعب بالعداد العام. إذا كان للمقياس قيمة تجارية، فأضف ما يلي:
- تحديد معدل الطلبات (على مستوى الخادم الوكيل العكسي/جدار حماية تطبيقات الويب)
- التخزين لكل مستخدم (مسجل الدخول) أو ملف تعريف الارتباط الموقع
- كشف الروبوتات
هاملت
- استعلام مشروط: تجنب التحميل
like.jsفي كل مكان. - تجنب طلبات REST غير الضرورية: يمكنك إضافة تأخير إذا سمحت بالنقرات المتعددة (هنا نقوم بالتبديل، لذلك يكون الأمر مستقرًا).
- في حالة ارتفاع حركة المرور: استخدم بيانات التعريف بعد التحديث غير الذري. إذا واجهت ارتفاعات مفاجئة، فاستخدم جدولًا مخصصًا وتحديث SQL ذري (أو تجميعًا مؤجلًا).
الدورية
- حافظ على المتجر في مساحة اسم واضحة (
bpcab/likeتجنب الأسماء العامة (app). - تجنب ربط منطقك بأداة بناء. أنشئ طبقة تكامل (أداة/وحدة نمطية) إذا لزم الأمر.
- يمكن مراقبة تطورات واجهة برمجة تطبيقات التفاعل عبر Gutenberg (طلب السحب) ودليل المستخدم. تصل التغييرات أولاً إلى جانب Gutenberg، ثم يتم دمجها في النظام الأساسي.
الموارد
- واجهة برمجة تطبيقات التفاعل (developer.wordpress.org)
- دليل واجهة برمجة تطبيقات REST
- register_rest_route() (مرجع)
- wp_create_nonce() (مرجع)
- wp_verify_nonce() (مرجع)
- نظام تتبع الأخطاء في ووردبريس الأساسي (Trac)
- مستودع غوتنبرغ (التفاعلية العلائقية)
- filter_var() (php.net)
- منتديات دعم WordPress.org (حالات واقعية)
الأسئلة الشائعة
هل تحل واجهة برمجة التطبيقات التفاعلية محل React في Gutenberg؟
لا، لا يزال غوتنبرغ يستخدم رياكت للمحرر. تهدف واجهة برمجة التطبيقات التفاعلية بشكل أساسي إلى التفاعل بين الكتل/المكونات في واجهة المستخدم، وذلك بنموذج أبسط وأكثر وضوحًا.
لماذا بلدي window.wp.interactivity غير مُعرَّف؟
عمليًا، قد يكون هذا خطأً في ترتيب التحميل (تحميل البرنامج النصي مبكرًا جدًا أو عدم تحميله إطلاقًا)، أو خطأً في تحسين جافا سكريبت يُخلّ بالترتيب، أو خطأً في أداة إنشاء المحتوى التي تُولّد المحتوى دون استيفاء الشروط المطلوبة. عطّل مؤقتًا عمليات التصغير/التأجيل المكثفة، وتحقق من إعدادات الشبكة.
هل هذا متوافق مع ذاكرة التخزين المؤقت للصفحة الكاملة؟
نعم، لعرض الصفحة. أما بالنسبة لحفظ البيانات، فكن حذرًا بشأن قيمة nonce: إذا تم تحميل صفحة HTML من ذاكرة تخزين مؤقتة وتحتوي على قيمة nonce منتهية الصلاحية، فستفشل طلبات REST POST (رمز الخطأ 403). في المواقع التي تعتمد بشكل كبير على ذاكرة التخزين المؤقت، أفضل إنشاء قيمة nonce عبر نقطة نهاية عامة (للقراءة فقط) أو حصر هذه الخاصية على المستخدمين المسجلين.
لماذا نستخدم REST بدلاً من admin-ajax؟
يوفر REST دلالات HTTP أكثر وضوحًا، واستجابات JSON قياسية، ويتكامل بشكل أفضل مع الأدوات الحديثة. لا يزال admin-ajax صالحًا، لكنه سرعان ما يصبح حلاً شاملاً يصعب تأمينه وتحليله.
هل يمكننا استخدام كتلة غوتنبرغ بدلاً من رمز مختصر؟
نعم، وهذا غالباً ما يكون مفضلاً. يبقى جوهر النمط كما هو: عرض SSR + السمات data-wp-* + المتجر. أستخدم هنا رمزًا مختصرًا حتى تتمكن من اختباره في 5 دقائق، بما في ذلك في أدوات الإنشاء.
كيفية إدارة عدة عدادات مختلفة (الإعجابات، الإشارات المرجعية، الأصوات)؟
استخدم مساحات أسماء منفصلة (bpcab/like, bpcab/bookmarkأو مخزن بيانات واحد مُهيأ حسب السياق. تجنب استخدام مخزن بيانات "متجانس" إذا كانت مكوناتك مستقلة.
هل العداد الموجود في بيانات المنشور موثوق؟
بالنسبة لمدونة عادية: نعم. أما بالنسبة للمدونات ذات الزيارات العالية: لا، ستحدث تعارضات. انتقل إلى جدول مخصص وتحديثات ذرية، أو استخدم استراتيجية تجميع غير متزامنة.
كيف يمكنني اختبار هذا الكود بشكل صحيح؟
أفعل عادةً ما يلي:
- سجل تصحيح الأخطاء في بيئة الاختبار + WP_DEBUG
- اختبار يدوي: نقرات سريعة، تنقل، ذاكرة التخزين المؤقت للمتصفح
- اختبار REST: curl (403 متوقع بدون nonce، 200 مع nonce)
- اختبار توافق ذاكرة التخزين المؤقت: قم بتمكين ذاكرة التخزين المؤقت، وتحقق من انتهاء صلاحية رمز التحقق (nonce).
لماذا لا يتم وضع اسم المندوب البابوي في الداخل؟ data-wp-context ?
يمكنك فعل ذلك، لكنك ستكرر قيمة حساسة في كل حالة. أفضل استخدام كائن عام بسيط، ثم سياق لكل مكون لحالة واجهة المستخدم. إذا كانت لديك صفحات طويلة جدًا، فهذا يقلل من حجم ملف HTML.
هل يعمل في قالب كلاسيكي (غير FSE)؟
نعم. لا تقتصر واجهة برمجة تطبيقات التفاعل على قوالب الكتل. المهم هو تحميل الأصول بشكل صحيح وعرض الترميز مع السمات. data-wp-*.
ماذا أفعل إذا تسبب أحد مكونات البرنامج المساعد في تعطل الكود؟
تجنّب لصق هذه الإضافة في مقتطف برمجي. أنشئ إضافةً مناسبة (مثل هذه). صحيح أن المقتطفات البرمجية مفيدة، لكنني رأيتُ العديد من المواقع تتعطل بسبب تفعيل مقتطف برمجي على إصدار PHP مختلف، أو بسبب عملية نسخ ولصق مع وجود قوس معقوف مفقود.