في المقال السابق تعرفنا على WordPress Abilities API، وهي الواجهة التي أضافها ووردبريس لتوفير طريقة موحدة لتعريف الوظائف التي يستطيع الموقع أو إحدى إضافاته تنفيذها، مع وصف مدخلاتها ومخرجاتها والصلاحيات المطلوبة لاستخدامها. لكن فهم الفكرة نظريًا لا يكفي، خصوصًا أن القيمة الحقيقية لهذه الواجهة تظهر عندما نبدأ باستخدامها في مشروع فعلي.
في هذا الشرح سننشئ أول Ability خاصة بنا باستخدام
سنطلق على الوظيفة اسم
في هذا الشرح سننشئ أول Ability خاصة بنا باستخدام
wp_register_ability()، ولن نكتفي بمثال تجريبي يعيد جملة أو قيمة ثابتة، بل سنبني وظيفة يمكن استخدامها فعلًا: جلب أحدث المقالات المنشورة في موقع ووردبريس، مع إمكانية تحديد عدد المقالات التي نريد إرجاعها.سنطلق على الوظيفة اسم
iinkor/get-latest-posts، وسنستخدم المثال نفسه لاحقًا عندما ننتقل إلى REST API والتكامل مع الأدوات الخارجية.قبل البدء: كيف سنبني الـAbility؟
تتكون الـAbility التي سننشئها من جزأين أساسيين. الجزء الأول يصف الوظيفة لووردبريس: اسمها، وتصنيفها، والمدخلات التي تستقبلها، والبيانات التي تعيدها، والصلاحيات اللازمة لاستخدامها. أما الجزء الثاني فهو الكود الفعلي المسؤول عن جلب المقالات من قاعدة البيانات.
سنحتاج كذلك إلى إنشاء Category خاصة بنا لتنظيم الـAbilities. لذلك ستكون عملية البناء مرتبة على النحو التالي: نسجل التصنيف أولًا، ثم نسجل الـAbility ونحدد خصائصها، وبعد ذلك نكتب الوظيفة التي تنفذ عملية جلب المقالات، وأخيرًا نجرب تشغيلها من PHP.
يتطلب هذا الشرح استخدام WordPress 6.9 أو إصدار أحدث، لأن Abilities API أصبحت جزءًا من نواة ووردبريس ابتداءً من الإصدار 6.9.
ويفضل عند تجربة الأكواد عدم وضعها مباشرة في functions.php الخاص بالقالب الفعّال، وإنما استخدام إضافة مخصصة للتجربة أو بيئة تطوير، حتى لا يرتبط الكود بالقالب أو يتسبب خطأ برمجي في تعطيل الموقع الإنتاجي.
سنحتاج كذلك إلى إنشاء Category خاصة بنا لتنظيم الـAbilities. لذلك ستكون عملية البناء مرتبة على النحو التالي: نسجل التصنيف أولًا، ثم نسجل الـAbility ونحدد خصائصها، وبعد ذلك نكتب الوظيفة التي تنفذ عملية جلب المقالات، وأخيرًا نجرب تشغيلها من PHP.
يتطلب هذا الشرح استخدام WordPress 6.9 أو إصدار أحدث، لأن Abilities API أصبحت جزءًا من نواة ووردبريس ابتداءً من الإصدار 6.9.
ويفضل عند تجربة الأكواد عدم وضعها مباشرة في functions.php الخاص بالقالب الفعّال، وإنما استخدام إضافة مخصصة للتجربة أو بيئة تطوير، حتى لا يرتبط الكود بالقالب أو يتسبب خطأ برمجي في تعطيل الموقع الإنتاجي.
تسجيل تصنيف للـAbilities
يوفر ووردبريس نظام Categories لتنظيم الـAbilities المسجلة. وقد لا تبدو هذه الخطوة مهمة عندما نمتلك Ability واحدة فقط، لكنها تصبح مفيدة جدًا عندما تحتوي إضافة أو موقع على عدد كبير من الوظائف.
سننشئ تصنيفًا باسم
هناك تفصيل مهم في هذا الكود، وهو استخدام:
ووردبريس يوفر هذا الـHook خصيصًا لتسجيل تصنيفات Abilities، ولذلك لا ينبغي تسجيلها بصورة عشوائية عند
استخدمنا
الآن أصبح لدينا المكان الذي سنضع فيه Ability الخاصة بجلب المقالات.
سننشئ تصنيفًا باسم
iinkor-content، ونضع فيه الوظائف المتعلقة بالمحتوى:
كود:
add_action( 'wp_abilities_api_categories_init', 'iinkor_register_ability_categories' );
function iinkor_register_ability_categories() {
wp_register_ability_category(
'iinkor-content',
array(
'label' => __( 'Inkor Content', 'iinkor' ),
'description' => __( 'Abilities for working with WordPress content.', 'iinkor' ),
)
);
}
هناك تفصيل مهم في هذا الكود، وهو استخدام:
wp_abilities_api_categories_initووردبريس يوفر هذا الـHook خصيصًا لتسجيل تصنيفات Abilities، ولذلك لا ينبغي تسجيلها بصورة عشوائية عند
init. كما أن تسجيل التصنيفات يحدث قبل تسجيل الـAbilities نفسها، حتى تكون الفئة موجودة عندما نحاول إسناد إحدى الوظائف إليها.استخدمنا
iinkor-content كمعرّف للتصنيف، بينما يوفر label الاسم القابل للعرض، ويشرح description طبيعة الوظائف التي ستنتمي إليه.الآن أصبح لدينا المكان الذي سنضع فيه Ability الخاصة بجلب المقالات.
إنشاء Ability باستخدام wp_register_ability()
يتم تسجيل Ability من جهة PHP باستخدام الدالة
قد يبدو التعريف طويلًا مقارنة بالمهمة البسيطة التي نريد تنفيذها، لكن هذا في الحقيقة يوضح فلسفة Abilities API جيدًا. جزء كبير من الكود لا ينفذ العملية نفسها، وإنما يصف الوظيفة بطريقة يستطيع ووردبريس والأنظمة الأخرى فهمها.
لننظر إلى أهم أجزاء هذا التعريف.
wp_register_ability() داخل الـHook المخصص wp_abilities_api_init. سنضيف الكود التالي:
PHP:
add_action( 'wp_abilities_api_init', 'iinkor_register_abilities' );
function iinkor_register_abilities() {
wp_register_ability(
'iinkor/get-latest-posts',
array(
'label' => __( 'Get Latest Posts', 'iinkor' ),
'description' => __( 'Returns the latest published WordPress posts.', 'iinkor' ),
'category' => 'iinkor-content',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'number' => array(
'type' => 'integer',
'description' => __( 'Number of posts to return.', 'iinkor' ),
'minimum' => 1,
'maximum' => 10,
'default' => 5,
),
),
),
'output_schema' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'title' => array(
'type' => 'string',
),
'url' => array(
'type' => 'string',
'format' => 'uri',
),
'date' => array(
'type' => 'string',
),
),
),
),
'execute_callback' => 'iinkor_get_latest_posts',
'permission_callback' => function () {
return current_user_can( 'read' );
},
'meta' => array(
'annotations' => array(
'readonly' => true,
),
),
)
);
}
قد يبدو التعريف طويلًا مقارنة بالمهمة البسيطة التي نريد تنفيذها، لكن هذا في الحقيقة يوضح فلسفة Abilities API جيدًا. جزء كبير من الكود لا ينفذ العملية نفسها، وإنما يصف الوظيفة بطريقة يستطيع ووردبريس والأنظمة الأخرى فهمها.
لننظر إلى أهم أجزاء هذا التعريف.
الاسم والتصنيف
أطلقنا على الـAbility الاسم: iinkor/get-latest-posts. تتبع أسماء Abilities صيغة
وجود Namespace يمنع تعارض الأسماء بين الإضافات. فمن الممكن أن توفر إضافة أخرى وظيفة تسمى
بعد ذلك ربطناها بالتصنيف الذي سجلناه سابقًا:
'category' => 'iinkor-content',
وبذلك أصبح ووردبريس يعرف أن هذه الوظيفة تنتمي إلى مجموعة الوظائف الخاصة بالمحتوى.
namespace/ability-name. الجزء الأول iinkor هو الـNamespace الخاص بنا، والثاني get-latest-posts يصف الوظيفة التي تنفذها الـAbility.وجود Namespace يمنع تعارض الأسماء بين الإضافات. فمن الممكن أن توفر إضافة أخرى وظيفة تسمى
get-latest-posts، لكن استخدام Namespace مختلف يجعل كل Ability مستقلة.بعد ذلك ربطناها بالتصنيف الذي سجلناه سابقًا:
'category' => 'iinkor-content',
وبذلك أصبح ووردبريس يعرف أن هذه الوظيفة تنتمي إلى مجموعة الوظائف الخاصة بالمحتوى.
تحديد المدخلات باستخدام input_schema
نريد أن يستطيع من يستخدم الـAbility تحديد عدد المقالات التي يريد الحصول عليها. لذلك أضفنا مدخلًا باسم
الفكرة هنا أعمق من مجرد استقبال متغير داخل Function. نحن نصف نوع البيانات وحدودها ومعناها بطريقة منظمة باستخدام JSON Schema. وهذا يسمح للأدوات التي تكتشف الـAbility بمعرفة البيانات التي تحتاج إلى إرسالها دون قراءة الكود الداخلي الذي ينفذ الوظيفة.
number داخل input_schema. حددنا أن هذا المدخل يجب أن يكون عددًا صحيحًا، وأن أقل قيمة مسموحة هي 1 وأكبر قيمة هي 10، بينما القيمة الافتراضية هي 5.
PHP:
'number' => array(
'type' => 'integer',
'description' => __( 'Number of posts to return.', 'iinkor' ),
'minimum' => 1,
'maximum' => 10,
'default' => 5,
),
الفكرة هنا أعمق من مجرد استقبال متغير داخل Function. نحن نصف نوع البيانات وحدودها ومعناها بطريقة منظمة باستخدام JSON Schema. وهذا يسمح للأدوات التي تكتشف الـAbility بمعرفة البيانات التي تحتاج إلى إرسالها دون قراءة الكود الداخلي الذي ينفذ الوظيفة.
وصف النتيجة باستخدام output_schema
مثلما وصفنا البيانات التي تدخل إلى الـAbility، نصف أيضًا البيانات التي ستخرج منها. حددنا أن النتيجة ستكون
وبذلك يستطيع النظام الذي يتعامل مع الـAbility معرفة شكل النتيجة المتوقعة مسبقًا، وليس فقط بعد تنفيذ الوظيفة.
Array تحتوي على مجموعة من العناصر، وأن كل عنصر يمثل مقالًا ويتضمن ثلاثة حقول: title للعنوان، وurl للرابط، وdate لتاريخ النشر.وبذلك يستطيع النظام الذي يتعامل مع الـAbility معرفة شكل النتيجة المتوقعة مسبقًا، وليس فقط بعد تنفيذ الوظيفة.
كتابة الوظيفة التي تجلب المقالات
حتى الآن قمنا بتعريف الـAbility، لكننا لم نكتب المنطق الذي سينفذ المهمة نفسها. ولهذا حددنا في التعريف:
سننشئ الآن الدالة
في بداية الدالة نحصل على قيمة
بعد ذلك نستخدم
ثم نمر على المقالات ونبني النتيجة بالشكل الذي حددناه سابقًا في
وهنا تتضح نقطة مهمة: Abilities API لا تستبدل WP_Query أو دوال WordPress التقليدية. نستمر في استخدام الأدوات نفسها لتنفيذ الوظيفة؛ الجديد هو أننا أصبحنا نغلف هذه الوظيفة بتعريف منظم يجعلها قابلة للاكتشاف والفهم والاستخدام بواسطة أجزاء أخرى من النظام.
'execute_callback' => 'iinkor_get_latest_posts',سننشئ الآن الدالة
iinkor_get_latest_posts()، وهي المسؤولة عن استخدام WordPress لجلب المقالات:
PHP:
function iinkor_get_latest_posts( $input ) {
$number = isset( $input['number'] )
? absint( $input['number'] )
: 5;
$number = max( 1, min( 10, $number ) );
$query = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $number,
'no_found_rows' => true,
)
);
$posts = array();
foreach ( $query->posts as $post ) {
$posts[] = array(
'title' => get_the_title( $post ),
'url' => get_permalink( $post ),
'date' => get_the_date( 'Y-m-d', $post ),
);
}
return $posts;
}
في بداية الدالة نحصل على قيمة
number التي أرسلها المستخدم، ثم نحولها إلى عدد صحيح باستخدام absint(). وضعنا كذلك حدًا نهائيًا بين 1 و10 حتى لا نعتمد فقط على صحة المدخل القادم.بعد ذلك نستخدم
WP_Query بالطريقة المعتادة لجلب المقالات المنشورة. تحدد posts_per_page عدد المقالات وفق القيمة التي استقبلناها، بينما استخدمنا no_found_rows لأننا لا نحتاج في هذه الحالة إلى حساب العدد الإجمالي للنتائج أو إنشاء Pagination.ثم نمر على المقالات ونبني النتيجة بالشكل الذي حددناه سابقًا في
output_schema: عنوان ورابط وتاريخ لكل مقال.وهنا تتضح نقطة مهمة: Abilities API لا تستبدل WP_Query أو دوال WordPress التقليدية. نستمر في استخدام الأدوات نفسها لتنفيذ الوظيفة؛ الجديد هو أننا أصبحنا نغلف هذه الوظيفة بتعريف منظم يجعلها قابلة للاكتشاف والفهم والاستخدام بواسطة أجزاء أخرى من النظام.
الصلاحيات: من يستطيع تنفيذ الـAbility؟
تحديد ما تستطيع الوظيفة فعله لا يكفي؛ يجب أن نحدد أيضًا من يستطيع تشغيلها. لهذا استخدمنا:
في مثالنا اشترطنا أن يمتلك المستخدم صلاحية
لذلك يجب التعامل مع الصلاحيات منذ مرحلة تصميم الـAbility، وليس إضافتها كإجراء ثانوي بعد الانتهاء من الكود. حددنا أيضًا داخل meta أن الوظيفة:
أي أنها تقرأ البيانات ولا تغير حالة الموقع. هذه المعلومة تساعد الأنظمة التي تتعامل مع الـAbility على فهم طبيعة العملية قبل تنفيذها، وهو أمر يصبح مهمًا بصورة خاصة عند استخدام أدوات الأتمتة ووكلاء الذكاء الاصطناعي.
PHP:
'permission_callback' => function () {
return current_user_can( 'read' );
},
في مثالنا اشترطنا أن يمتلك المستخدم صلاحية
read. وبما أن الوظيفة لا تعدل شيئًا وإنما تقرأ المقالات المنشورة فقط، فهذا يكفي لأغراض المثال. لكن أهمية permission_callback تصبح أوضح عندما نتخيل وظائف أكثر حساسية. Ability تعدل مقالًا يجب ألا تحصل على شروط الصلاحية نفسها التي تحصل عليها Ability تقرأ بيانات عامة، ووظيفة تحذف محتوى أو تغير إعدادات الموقع تحتاج إلى حماية أكبر.لذلك يجب التعامل مع الصلاحيات منذ مرحلة تصميم الـAbility، وليس إضافتها كإجراء ثانوي بعد الانتهاء من الكود. حددنا أيضًا داخل meta أن الوظيفة:
'readonly' => true,.أي أنها تقرأ البيانات ولا تغير حالة الموقع. هذه المعلومة تساعد الأنظمة التي تتعامل مع الـAbility على فهم طبيعة العملية قبل تنفيذها، وهو أمر يصبح مهمًا بصورة خاصة عند استخدام أدوات الأتمتة ووكلاء الذكاء الاصطناعي.
استدعاء الـAbility وتنفيذها
بعد تسجيل الوظيفة نستطيع الحصول عليها من سجل Abilities باستخدام
إذا كانت الـAbility مسجلة بصورة صحيحة فسنحصل على الكائن الخاص بها، وبعد ذلك يمكن تنفيذها وإرسال المدخلات المطلوبة. للحصول على أحدث ثلاثة مقالات مثلًا:
ومن الأفضل دائمًا التحقق من النتيجة قبل استخدامها:
إذا نجح التنفيذ ستحتوي
بهذا نكون قد أكملنا الدورة الأساسية للـAbility: تعريفها، تسجيلها، تنفيذ منطقها، ثم استدعاؤها.
wp_get_ability():
PHP:
$ability = wp_get_ability( 'iinkor/get-latest-posts' );
if ( ! $ability ) {
return;
}
إذا كانت الـAbility مسجلة بصورة صحيحة فسنحصل على الكائن الخاص بها، وبعد ذلك يمكن تنفيذها وإرسال المدخلات المطلوبة. للحصول على أحدث ثلاثة مقالات مثلًا:
PHP:
$result = $ability->execute(
array(
'number' => 3,
)
);
ومن الأفضل دائمًا التحقق من النتيجة قبل استخدامها:
PHP:
if ( is_wp_error( $result ) ) {
error_log( $result->get_error_message() );
return;
}
إذا نجح التنفيذ ستحتوي
$result على Array تضم المقالات الثلاثة، وكل مقال يحتوي على title وurl وdate وفق البنية التي حددناها مسبقًا.بهذا نكون قد أكملنا الدورة الأساسية للـAbility: تعريفها، تسجيلها، تنفيذ منطقها، ثم استدعاؤها.
الكود الكامل للـAbility
إذا أردت تطبيق المثال مباشرة، فهذا هو الكود كاملًا بعد جمع الأجزاء السابقة:
يمكن وضع هذا المثال داخل إضافة مخصصة في بيئة التجربة. ومن الأفضل عدم تعديل ملفات WordPress Core مطلقًا، لأن أي تعديل عليها سيضيع مع التحديثات وقد يسبب مشكلات في الموقع.
PHP:
<?php
add_action( 'wp_abilities_api_categories_init', 'iinkor_register_ability_categories' );
function iinkor_register_ability_categories() {
wp_register_ability_category(
'iinkor-content',
array(
'label' => __( 'Inkor Content', 'iinkor' ),
'description' => __( 'Abilities for working with WordPress content.', 'iinkor' ),
)
);
}
add_action( 'wp_abilities_api_init', 'iinkor_register_abilities' );
function iinkor_register_abilities() {
wp_register_ability(
'iinkor/get-latest-posts',
array(
'label' => __( 'Get Latest Posts', 'iinkor' ),
'description' => __( 'Returns the latest published WordPress posts.', 'iinkor' ),
'category' => 'iinkor-content',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'number' => array(
'type' => 'integer',
'description' => __( 'Number of posts to return.', 'iinkor' ),
'minimum' => 1,
'maximum' => 10,
'default' => 5,
),
),
),
'output_schema' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'title' => array(
'type' => 'string',
),
'url' => array(
'type' => 'string',
'format' => 'uri',
),
'date' => array(
'type' => 'string',
),
),
),
),
'execute_callback' => 'iinkor_get_latest_posts',
'permission_callback' => function () {
return current_user_can( 'read' );
},
'meta' => array(
'annotations' => array(
'readonly' => true,
),
),
)
);
}
function iinkor_get_latest_posts( $input ) {
$number = isset( $input['number'] )
? absint( $input['number'] )
: 5;
$number = max( 1, min( 10, $number ) );
$query = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $number,
'no_found_rows' => true,
)
);
$posts = array();
foreach ( $query->posts as $post ) {
$posts[] = array(
'title' => get_the_title( $post ),
'url' => get_permalink( $post ),
'date' => get_the_date( 'Y-m-d', $post ),
);
}
return $posts;
}
يمكن وضع هذا المثال داخل إضافة مخصصة في بيئة التجربة. ومن الأفضل عدم تعديل ملفات WordPress Core مطلقًا، لأن أي تعديل عليها سيضيع مع التحديثات وقد يسبب مشكلات في الموقع.
ماذا بنينا بالفعل؟
لو كتبنا دالة
لكن ما أضفناه باستخدام Abilities API هو الطبقة المحيطة بهذه الوظيفة. أصبح لها اسم موحد، وتصنيف، ووصف، ومدخلات ومخرجات معرّفة، وقاعدة صلاحيات، ومعلومة توضح أنها وظيفة للقراءة فقط. والأهم أنها أصبحت مسجلة داخل نظام يستطيع بقية ووردبريس والأدوات المتوافقة اكتشافه والتعامل معه.
وهذا هو السبب في أن المثال لا ينتهي بمجرد نجاح
في الخطوة التالية سنستخدم نفس
وهكذا سنطور Ability واحدة على مدار السلسلة، من وظيفة بسيطة داخل PHP إلى وظيفة يمكن للأنظمة الخارجية وأدوات الأتمتة والذكاء الاصطناعي الاستفادة منها.
iinkor_get_latest_posts() وحدها لكانت لدينا وظيفة PHP عادية تجلب أحدث المقالات، وهذا شيء يستطيع مطورو ووردبريس فعله منذ سنوات.لكن ما أضفناه باستخدام Abilities API هو الطبقة المحيطة بهذه الوظيفة. أصبح لها اسم موحد، وتصنيف، ووصف، ومدخلات ومخرجات معرّفة، وقاعدة صلاحيات، ومعلومة توضح أنها وظيفة للقراءة فقط. والأهم أنها أصبحت مسجلة داخل نظام يستطيع بقية ووردبريس والأدوات المتوافقة اكتشافه والتعامل معه.
وهذا هو السبب في أن المثال لا ينتهي بمجرد نجاح
WP_Query.في الخطوة التالية سنستخدم نفس
iinkor/get-latest-posts بدل إنشاء مثال جديد، وسنشرح كيفية اختبار الـAbility والتعامل معها عبر REST API، وما الذي يحدث عند إتاحتها لعميل خارجي، وكيف تظل المصادقة والصلاحيات جزءًا أساسيًا من العملية.وهكذا سنطور Ability واحدة على مدار السلسلة، من وظيفة بسيطة داخل PHP إلى وظيفة يمكن للأنظمة الخارجية وأدوات الأتمتة والذكاء الاصطناعي الاستفادة منها.
