⏱️ جاري الحساب…

شرح حالات المحتوى Post Status في ووردبريس: دورة حياة النشر وخطافات الانتقال والحالات المخصصة

رسم مفاهيمي يوضح حالات المحتوى في ووردبريس ضمن سلسلة تطوير ووردبريس
📋 فهرس محتويات المقال (اضغط للتنقل السريع) ▾

أهلاً بيك في المقال رقم 23 من 100 في سلسلتنا المعمارية معمارية ووردبريس من الأساسيات إلى الاحتراف الكامل، ضمن المستوى الثالث: نموذج بيانات ووردبريس (WordPress Data Modeling). النهاردة هنشرح عنصر من أهم العناصر المتحكمة في أمان ورؤية ودورة حياة المحتوى على ووردبريس: حالات المحتوى (Post Statuses) وآلية انتقالات النشر (Status Transitions).

لو سألت مطور مبتدئ: “يعني إيه مقال منشور أو مسودة؟”، هيقولك: “دي مجرد كلمة مكتوبة جنب المقال عشان نعرف حالته!”. لكن كمهندس برمجيات ووردبريس محترف، أنت عارف إن الـ Post Status مش مجرد صفة أو تصنيف (Taxonomy)؛ دي عبارة عن آلة حالة منتهية (Finite State Machine – FSM) بتحكم قواعد الوصول (Access Control)، وتحدد مين يشوف إيه، وتوجه استعلامات الـ SQL في قاعدة البيانات، وتشغل خطافات برمجية (Hooks) حرجة جداً بتتبنى عليها أنظمة الإشعارات الفورية، والنشر التلقائي على السوشيال ميديا، وإعادة بناء الكاش، وسير العمل التحريري (Editorial Workflows) في كبرى المؤسسات الصحفية والشركات.

المعمارية التحتية لحالات المحتوى في قاعدة البيانات (MySQL Layer)

في قاعدة بيانات ووردبريس، جدول wp_posts بيحتوي على عمود أساسي اسمه post_status نوعه varchar(20). العمود ده مش متروك للصدفة؛ في المعمارية الافتراضية، ووردبريس عامل عليه فهرس مركب (Composite Index) اسمه type_status_date بيجمع بين ثلاثة أعمدة:

KEY `type_status_date` (`post_type`, `post_status`, `post_date`, `ID`)

الفهرس ده هو سر سرعة استعلامات ووردبريس؛ لما زائر يفتح الصفحة الرئيسية للموقع، ووردبريس بيعمل استعلام يبحث عن المقالات اللي نوعها post_type = 'post' وحالتها post_status = 'publish' ومرتبة تنازلياً حسب post_date. بفضل الفهرس المركب ده، محرك MySQL بيجيب النتائج في أجزاء من الميلي ثانية حتى لو الجدول فيه ملايين المقالات، لأنه مش بيحتاج يعمل Full Table Scan.

لكن لما المحتوى حالته تتغير لمسودة (Draft) أو خاص (Private) أو يروح سلة المهملات (Trash)، ووردبريس بيغير قيمة العمود ده فوراً، وبالتالي بيختفي تماماً وبشكل تلقائي من استعلامات الواجهة الأمامية دون الحاجة لأي فلترة إضافية في القالب.

التشريح التفصيلي لحالات المحتوى الافتراضية في ووردبريس

ووردبريس بيوفر مجموعة من الحالات الأساسية المدمجة في النواة (Core Built-in Statuses). كل حالة ليها منطق هندسي وسلوك أمني مستقل تماماً عن التانية:

1. الحالة المنشورة (publish)

دي الحالة النهائية للمحتوى العام. المقال بيكون متاح لكل الزوار ولعناكب محركات البحث ولواجهات الـ REST API بدون الحاجة لأي جلسة تسجيل دخول (Authentication). بتدخل تلقائياً في خلاصات الموقع (RSS Feeds) وخرائط الموقع (XML Sitemaps) والأرشيفات.

2. المحتوى المجدول (future) وكابوس Missed Schedule

لما الكاتب يحدد تاريخ نشر مستقبلي للمقال، ووردبريس بيعين حالته كـ future. في الحالة دي، المقال مبيظهرش للعامة. ووردبريس بيسجل مهمة مجدولة في نظام الكرون الداخلي (WP-Cron) باسم publish_future_post. أول ما ييجي الميعاد والزوار يزوروا الموقع، الكرون بيشتغل ويغير الحالة من future إلى publish عبر دالة check_and_publish_future_post($post_id).

ليه بيحصل خطأ “Missed schedule” الشهير في المواقع؟ لأن WP-Cron الافتراضي مش Real System Cron؛ هو بيعتمد على زيارات الزوار! لو موقعك مفيهوش زيارات لحظة ميعاد النشر، أو لو مشغل Page Caching كامل بيمنع وصول الطلبات لـ PHP، الكرون مش هينبض والمقال هيفضل معلق في حالة future. الحل الهندسي هو تعطيل الكرون الافتراضي بـ define('DISABLE_WP_CRON', true); وتشغيل System Crontab على السيرفر كل دقيقة.

3. المسودات (draft) والمراجعة المعلقة (pending)

المسودة (draft) هي مساحة عمل الكاتب الخاصة؛ لا يراها إلا صاحبها أو من يملك صلاحية edit_others_posts. أما حالة (pending) فهي مخصصة لدور المساهم (Contributor)؛ المساهم يقدر يكتب المقال لكن معندوش زرار “نشر”، عنده زرار اسمه “إرسال للمراجعة”. أول ما يضغط عليه، الحالة بتتحول لـ pending عشان يظهر في لوحة تحكم المحرر أو مدير الموقع لاعتماده.

4. المحتوى الخاص (private)

المقال بيكون محفوظ لكن مفيش أي زائر عادي يقدر يفتحه. لو زائر عادي فتح الرابط بتاعه هيجيله صفحة 404 Not Found عشان حتى ميقدرش يخمن إن الرابط ده موجود أصلاً! المقال ده بيظهر فقط للمستخدمين المسجلين اللي معاهم صلاحية read_private_posts (غالباً المدير والمحرر)، وعنوانه بيسبق بكلمة “خاص:” تلقائياً في القوالب الافتراضية.

5. سلة المهملات (trash) وهندسة تجنب تضارب الروابط

لما تمسح مقال في ووردبريس، هو مش بيتحذف من الـ Database فوراً؛ ده بيتعمله Soft Delete بنقله لحالة trash. المقال بيفضل في السلة لمدة 30 يوم (يمكن تعديلها عبر الثابت EMPTY_TRASH_DAYS) وبعدها ووردبريس بيمسحه نهائياً في خلفية الكرون.

الحركة العبقرية المعمارية اللي ووردبريس بيعملها هنا هي إنه لما ينقل المقال للسلة، بيعدل الـ post_name (الـ Slug) ويضيف له __trashed في آخره. ليه؟ عشان لو المحرر حب يعمل مقال جديد بنفس الرابط القديم، ميحصلش تضارب في الروابط الدائمة (Slug Collision)! ولو استرجعت المقال من السلة، ووردبريس بيرجع الـ Slug لأصله تلقائياً.

6. المسودات التلقائية (auto-draft) والتوريث (inherit)

  • auto-draft: أول ما تدوس على زرار “أضف مقالاً جديداً”، ووردبريس قبل ما تكتب حرف واحد بيروح يزرع صف جديد في wp_posts بحالة auto-draft. ده بيتعمل عشان ووردبريس يقدر يولد ID جاهز للمنشور يقدر يربط بيه الصور المرفوعة والحقول المخصصة قبل ما تحفظ المقال رسمياً. لو قفلت الصفحة بدون ما تكتب أي حاجة، ووردبريس بيمسح السجلات دي دورياً.
  • inherit: بتستخدمها المرفقات (Attachments) والمراجعات (Revisions) عشان تورث نفس حالة المنشور الأب؛ يعني لو المقال الأب منشور، المرفقات متاحة، ولو المقال اتحذف، الصلاحيات بتتورث طبقاً للأب.

مصفوفة المقارنة الهندسية لحالات المحتوى

الجدول التالي يلخص السلوك الفني لكل حالة من حالات المحتوى في النواة:

الحالة (Status)الرؤية في الواجهة (Public)الظهور في WP_Query العاديفهارس السيو وSitemapsالصلاحية المطلوبة للرؤية
publishنعم – متاح للجميعنعم (افتراضي)نعملا تتطلب تسجيل دخول
futureلا (إلا للمحررين)لالاedit_posts
draftلالالاedit_posts للمؤلف أو المحرر
pendingلالالاedit_others_posts
privateلا (يعطي 404 لغير المصرح)لالاread_private_posts
trashلا (يعطي 404)لالاdelete_posts
auto-draftلا نهائياًلالاخاص بالنواة فقط
inheritتتبع المنشور الأبتتبع الأبتتبع الأبتتبع المنشور الأب

هندسة خطافات الانتقال (Transition Hooks) وتجنب الحلقات اللانهائية

من أكتر الأخطاء الشائعة اللي بنشوفها في كود المطورين، إن المطور لما يحب ينفذ كود عند نشر المقال (مثلاً يبعت إيميل للمشتركين أو يبعت إشعار لـ Webhook خارجي)، بيروح يربط الكود بخطاف save_post ويفحص هل الحالة publish ولا لأ! ده خطأ معماري فادح لثلاثة أسباب:

  1. خطاف save_post بيتنفذ في كل عملية حفظ (مسودة، مراجعة تلقائية Autosave، تعديل مقال قديم منشور بالفعل). فلو عندك مقال منشور وعدلت فيه حرف غلطة إملائية وعملت تحديث، الإيميل هيتبعت لكل المشتركين تاني وتالت ورابع!
  2. لو دالة الإرسال دي جواها كود بيعمل wp_update_post()، هتدخل في حلقة تكرار لانهائية (Infinite Loop) لأن التحديث هيستدعي save_post اللي هيستدعي التحديث وهكذا لحد ما السيرفر يموت من الـ Memory Exhaustion.
  3. save_post ميعرفش الحالة القديمة كانت إيه؛ هو يعرف فقط الحالة بعد الحفظ.

سلسلة خطافات النشر الصحيحة في ووردبريس

ووردبريس بيوفر نظام خطافات عبقري لانتقالات الحالة بيضمن تنفيذ الأكشن مرة واحدة بس وفقط عند الانتقال الفعلي بين حالتين:

  • الخطاف العام الشامل: transition_post_status($new_status, $old_status, $post): ده بيشتغل مع أي انتقال بين أي حالتين في ووردبريس.
  • الخطاف المزدوج المحدد: {$old_status}_to_{$new_status}: زي draft_to_publish أو pending_to_publish أو publish_to_trash.
  • خطاف النشر المحدد لنوع المحتوى: publish_{$post_type}: زي publish_post أو publish_property.

كود عملي متقدم: إدارة انتقالات النشر ونظام الإشعارات الآمن

الكود التالي بيوريك إزاي تطبق نظام إشعارات احترافي للناشرين لما مقال ينتقل من مسودة أو مراجعة إلى النشر لأول مرة فقط، مع استخدام علامة مانعة للتكرار (Idempotency Post Meta) لتجنب أي إشعارات مكررة نهائياً:

<?php
/**
 * مراقبة انتقال حالة المنشور وإرسال إشعار فوري عند النشر لأول مرة فقط
 *
 * @param string  $new_status الحالة الجديدة
 * @param string  $old_status الحالة السابقة
 * @param WP_Post $post       كائن المنشور
 */
function abozaid_handle_post_publishing( string $new_status, string $old_status, WP_Post $post ): void {
    // 1. التأكد من أن نوع المحتوى هو المستهدف (مثلاً المقالات أو العقارات)
    if ( ! in_array( $post->post_type, [ 'post', 'real_estate_property' ], true ) ) {
        return;
    }

    // 2. التحقق من أن الانتقال هو انتقال فعلي للنشر من حالة غير منشورة
    if ( 'publish' !== $new_status || 'publish' === $old_status ) {
        return;
    }

    // 3. منع الإرسال المكرر باستخدام مفتاح أمان (Idempotency Key)
    $already_notified = get_post_meta( $post->ID, '_az_publish_notified', true );
    if ( $already_notified ) {
        return;
    }

    // 4. تسجيل علامة الإرسال في الميتا قبل تنفيذ العملية الخارجية
    update_post_meta( $post->ID, '_az_publish_notified', time() );

    // 5. إرسال الإشعار أو إطلاق الـ Webhook (نموذج محكم)
    $author = get_userdata( (int) $post->post_author );
    $author_name = $author ? $author->display_name : 'محرر مجهول';

    $log_message = sprintf(
        'تم نشر المقال بنجاح: "%s" (ID: %d) بواسطة: %s. الانتقال تم من [%s] إلى [%s].',
        $post->post_title,
        $post->ID,
        $author_name,
        $old_status,
        $new_status
    );

    // تسجيل العملية في سجل الأحداث أو إرسالها لقناة تيليجرام وسلاك
    error_log( '[Abozaid Publishing Pipeline] ' . $log_message );
}
add_action( 'transition_post_status', 'abozaid_handle_post_publishing', 10, 3 );
?>

تسجيل حالات محتوى مخصصة (Custom Post Statuses) باحترافية

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

دالة register_post_status($status, $args) هي المسؤولة عن تسجيل هذه الحالات في النواة. تعال نشوف الكود الكامل لتسجيل حالة “مؤرشف تاريخياً”:

<?php
/**
 * تسجيل حالة محتوى مخصصة للأرشفة التاريخية
 */
function abozaid_register_archived_post_status(): void {
    register_post_status( 'archived', [
        'label'                     => _x( 'مؤرشف تاريخياً', 'post status label', 'abozaid' ),
        'public'                    => true,  // الرابط يظل متاحاً للجمهور حتى لا نفقد روابط السيو
        'exclude_from_search'       => true,  // مستبعد من نتائج البحث العادية للموقع
        'show_in_admin_all_list'    => true,  // يظهر في تبويب "الكل" بلوحة التحكم
        'show_in_admin_status_list' => true,  // يظهر له تبويب مستقل بأعلى جدول المنشورات
        /* translators: %s: عدد المقالات المؤرشفة */
        'label_count'               => _n_noop(
            'مؤرشف <span class="count">(%s)</span>',
            'مؤرشف <span class="count">(%s)</span>',
            'abozaid'
        ),
    ] );
}
add_action( 'init', 'abozaid_register_archived_post_status' );

/**
 * دمج الحالة المخصصة في القائمة المنسدلة للتحرير السريع (Quick Edit)
 */
function abozaid_append_archived_status_to_quick_edit(): void {
    ?>
    <script>
    document.addEventListener("DOMContentLoaded", function() {
        const selectStatus = document.querySelectorAll("select[name='_status']");
        selectStatus.forEach(select => {
            if (!select.querySelector("option[value='archived']")) {
                const opt = document.createElement("option");
                opt.value = "archived";
                opt.text = "مؤرشف تاريخياً";
                select.appendChild(opt);
            }
        });
    });
    </script>
    <?php
}
add_action( 'admin_footer-edit.php', 'abozaid_append_archived_status_to_quick_edit' );
?>

دراسة حالة واقعية من السوق المصري: بوابة إخبارية ضخمة وكارثة الإشعارات المكررة

في أحد المواقع الإخبارية الرياضية الشهيرة في مصر، كان عندهم تطبيق موبايل مربوط بووردبريس وقاعدة بيانات تضم 300,000 مشترك في خدمة الـ Push Notifications الفورية عبر Firebase Cloud Messaging (FCM). فجأة في ليلة مباراة قمة مهمة، الجمهور اشتكى إن كل هدف بيتسجل في الماتش بيوصلهم عليه 15 إشعار متكرر على الموبايل في دقيقة واحدة!

التحقيق والتشخيص الهندسي

لما دخلنا الكود نراجع سبب المشكلة، اكتشفنا الآتي:

  • المطور كان رابط كود إرسال إشعار الـ FCM بخطاف save_post!
  • أثناء المباراة، كان رئيس التحرير قاعد على المقال، وكل ما يحصل حدث جديد (كارت أصفر، ضربة ركنية، تبديل لاعب) يكتب سطر ويعمل “تحديث”.
  • وفوق كده، كان في إضافة مراجعات تلقائية بتعمل Autosave كل 15 ثانية، فكل عملية Autosave أو Update كانت بتنادي خطاف save_post وتبعت دفعة إشعارات جديدة لـ 300 ألف موبايل! ده كلف العميل آلاف الدولارات في فواتير الخوادم والـ Third-party APIs وكان هيدمر سمعة التطبيق.

الحل المعماري الذكي والنتيجة

  1. تم استبدال خطاف save_post فوراً بخطاف transition_post_status.
  2. تم اشتراط أن يكون الانتقال حصراً من حالة draft أو pending أو future إلى publish، وتجاهل أي انتقال من publish إلى publish.
  3. تمت إضافة نظام قفل فريد (Atomic Meta Lock) بالـ Timestamp يمنع تنفيذ خط النشر لنفس المقال أكثر من مرة واحدة في تاريخه إلا إذا تم طلب إعادة الإرسال يدوياً عبر زر مستقل.

النتيجة بالأرقام: تم القضاء تماماً على الإشعارات المكررة (Zero Duplication)، وهبطت طلبات الـ HTTP الصادرة من الخادم أثناء تغطية المباريات بنسبة 93%، واستقر السيرفر دون أي تشنج.

الوصايا العشر للتعامل مع حالات المحتوى في ووردبريس

  1. لا تعتمد أبداً على save_post لإرسال إشعارات النشر: استخدم دائماً transition_post_status أو خطافات الانتقال المزدوجة لتفادي الحلقات المفرغة والإشعارات المكررة.
  2. استخدم Idempotency Keys دائماً: احفظ Meta Flag مع كل مقال يتم نشر إشعاراته لتضمن عدم إرساله مرتين حتى لو حدث Crash أو Retry في السيرفر.
  3. عطل WP-Cron واستخدم System Cron للمقالات المجدولة: تخلص من خطأ Missed Schedule إلى الأبد بربط wp-cron.php بسيرفر الكرون الحقيقي على لينكس.
  4. افحص دائماً المعاملين $new_status و $old_status معاً: لا تكتفِ بفحص الحالة الجديدة فقط؛ معرفة الحالة القادمة منها المقال هو ما يحدد السياق التجاري الدقيق.
  5. احذر من تغيير post_name أثناء النقل للسلة: تذكر دائماً أن ووردبريس يلحق __trashed بالـ slug تلقائياً لتفادي تضارب المسارات.
  6. لا تستخدم Custom Post Statuses بدون توفير واجهة مستخدم: تسجيل الحالة بـ register_post_status في PHP لا يظهرها تلقائياً في محرر Gutenberg دون كود جافاسكريبت مصاحب.
  7. استفد من حالة inherit للمرفقات: تذكر أن الصور والمستندات المرفقة بمقال خاص تصبح بدورها غير مفهرسة للعامة تبعاً لحالة الأب.
  8. احرص على أمان صلاحيات النشر: لا تسمح للمستخدمين بتخطي خطافات الحالات عبر تعديل مباشر لجدول SQL، بل استخدم دائماً wp_update_post() ليتم إطلاق الفلاتر وتدقيق الصلاحيات.
  9. استخدم wp_trash_post بدلاً من wp_delete_post: امنح عملاءك دائماً فرصة استرجاع المحتوى المحذوف من السلة إلا في الحالات المخصصة لتنظيف السجلات التلقائية.
  10. نظف السجلات القديمة دورياً: راقب عدد سجلات auto-draft في المواقع الضخمة وتأكد من أن ووردبريس يقوم بمسح المسودات المعلقة غير المستخدمة.

الأسئلة الشائعة حول حالات المحتوى في ووردبريس

التفاصيل العميقة لآلية الحذف المؤقت والحذف النهائي (Soft vs Hard Delete)

لما بتنادي دالة wp_trash_post() في ووردبريس، النظام مش بس بيغير post_status إلى trash وبيعدل الـ slug؛ ووردبريس بينفذ سلسلة عمليات كاش وتدقيق بيانات معقدة جداً:

  1. إطلاق خطاف wp_trash_post: النواة بتطلق الخطاف ده عشان تسمح للإضافات (زي إضافات السيو والتخزين المؤقت) إنها تاخد نسخة احتياطية من بيانات المنشور أو تنظف كاش الـ Edge في Cloudflare قبل ما المنشور يختفي.
  2. تخزين الحالة الأصلية في البيانات الوصفية: ووردبريس بيسجل حقل ميتا سري اسمه _wp_trash_meta_status بيحفظ فيه الحالة اللي كان عليها المنشور قبل الحذف (مثلاً هل كان publish أو draft). الحركة دي عبقرية لأنها بتضمن إنك لما تضغط “استعادة” (Untrash)، المقال يرجع لحالته الأصلية الحقيقية مش يتحول فجأة لمنشور عام بالخطأ!
  3. حفظ وقت الحذف _wp_trash_meta_time: بيتسجل فيه الـ Timestamp للحظة رمي المقال في السلة، وده اللي نظام الكرون بيعتمد عليه عشان يحسب الـ 30 يوم ويمسحه نهائياً.
  4. تفريغ كاش الكائن والأرشيفات: بيتم استدعاء clean_post_cache() لمسح المنشور من ذاكرة الرام ومسح عدادات التصنيفات (Term Counts) المرتبطة به حتى لا تظهر أرقام مضللة في صفحات الأرشيف.

أما لو استدعيت wp_delete_post(, true) للحذف النهائي، فووردبريس بيقوم بحملة تنظيف شاملة (Cascading Cleanup): بيمسح كل الصفوف المرتبطة في wp_postmeta، ويمسح كل المراجعات (Revisions)، ويمسح العلاقات مع التصنيفات في wp_term_relationships، ويحذف التعليقات المرتبطة بالكامل من wp_comments و wp_commentmeta. عشان كده الحذف النهائي عملية مكلفة جداً على خادم قاعدة البيانات في المواقع التي تحتوي على ملايين السجلات.

س1: ما هو الفرق الجوهري بين save_post و transition_post_status؟

خطاف save_post يتم استدعاؤه مع كل عملية حفظ لأي سجل (سواء مسودة أو حفظ تلقائي أو تحديث لمقال منشور)، ولا يمتلك معرفة بالحالة السابقة. بينما transition_post_status هو خطاف آلة الحالة الحقيقي؛ يستقبل الحالة الجديدة والسابقة وكائن المنشور، ويطلق فقط عند حدوث تحول فعلي في دورة حياة المحتوى.

س2: لماذا تفشل المقالات المجدولة أحياناً وتظهر رسالة “Missed schedule”؟

لأن نظام ووردبريس الافتراضي WP-Cron لا يعمل كسيرفر كرون حقيقي، بل يستيقظ فقط عند وجود زيارة لموقعك عبر المتصفح. إذا كان الموقع يستخدم تخزيناً مؤقتاً كاملاً للصفحات (Full Page Cache) أو لم تكن هناك زيارات وقت النشر، فلن ينطلق الكرون في الميعاد. الحل هو إعداد Cron حقيقي على خادم لينكس كل دقيقة.

س3: كيف يمكنني تنفيذ كود عند انتقال مقال من مسودة إلى منشور حصراً؟

يمكنك استخدام الخطاف المباشر add_action('draft_to_publish', 'my_custom_function', 10, 1); الذي يمرر كائن المنشور مباشرة، أو استخدام transition_post_status مع فحص if ($new_status === 'publish' && $old_status === 'draft').

س4: هل يمكن إضافة حالة مخصصة جديدة مثل “قيد المراجعة الفنية”؟

نعم، عبر دالة register_post_status() في خطاف init. ولكن لعرضها في محرر كتل Gutenberg، يجب أيضاً تسجيل مكون جافاسكريبت عبر PluginPostStatusInfo لأن واجهة جوتنبرج الحديثة لا تقرأ الحالات المخصصة تلقائياً من PHP.

س5: ماذا يحدث معمارياً للرابط الدائم (Slug) عندما يتم إرسال المنشور إلى سلة المهملات (Trash)؟

ووردبريس يقوم تلقائياً بإعادة تسمية حقل post_name في قاعدة البيانات وإضافة اللاحقة __trashed في نهايته. هذا التصميم يتيح للمحرر إنشاء مقال جديد بنفس الرابط السابق دون أن يحدث تضارب في العناوين بجدول wp_posts.

س6: كم يوماً يبقى المقال في سلة المهملات قبل أن يتم حذفه نهائياً؟

المدة الافتراضية هي 30 يوماً. يمكنك تغيير هذه القيمة أو تعطيل السلة تماماً عبر إضافة الثابت define('EMPTY_TRASH_DAYS', 15); في ملف wp-config.php، أو تعيينه إلى 0 لحذف المقالات فوراً بدون سلة مهملات.

س7: ما هي فائدة حالة auto-draft وكيف تختلف عن draft العادية؟

حالة auto-draft تنشئها النواة في اللحظة التي تضغط فيها على “أضف جديداً” قبل أن تبدأ الكتابة، لتوليد معرف ID فريد للمنشور في قاعدة البيانات يمكن ربط الصور والميتا به. أما draft العادية فهي مسودة حقيقية كتب فيها المستخدم عنواناً أو محتوى وضغط على حفظ المسودة.

س8: كيف أمنع ظهور المقالات الخاصة (Private) من إظهار كلمة “خاص:” في العنوان بالقالب؟

عبر استخدام فلتر private_title_format وتمرير القيمة '%s' مباشرة بدون الكلمة المسبقة، بالشكل التالي: add_filter('private_title_format', fn() => '%s');.

س9: هل يمكن استعلام المقالات ذات الحالات المتعددة معاً في WP_Query؟

نعم بكل تأكيد، يمكنك تمرير مصفوفة في معامل post_status داخل الاستعلام، مثل: 'post_status' => ['publish', 'future', 'draft']، مع الانتباه إلى أن المستخدم الحالي يجب أن يمتلك الصلاحيات الكافية لرؤية المسودات.

س10: ما هي حالة inherit وفي أي أنواع محتوى تستخدم؟

تستخدم حالة inherit في المرفقات (Attachments) ومراجعات المنشورات (Revisions)؛ وتعني أن المنشور لا يملك حالة مستقلة بذاته بل يتبع ويرث نفس الحالة الأمنية وصلاحيات الرؤية الخاصة بالمنشور الأصلي المرتبط به.

س11: كيف أقوم بحذف مقال نهائياً فوراً وتخطي سلة المهملات برمجياً؟

عبر استدعاء دالة wp_delete_post($post_id, true); مع تمرير المعامل الثاني كـ true (Force Delete)، مما يجبر ووردبريس على مسح السجل وجميع الميتا والمرفقات التابعة له نهائياً من قاعدة البيانات دون نقله لـ trash.

س12: هل يؤثر عدد الحالات المخصصة على أداء قاعدة البيانات؟

لا يؤثر سلباً على قاعدة البيانات لأن الفهرس المركب type_status_date في MySQL يعالج أي قيمة نصية داخل عمود post_status بنفس الكفاءة والسرعة ما دامت القيمة أقل من 20 حرفاً.

س13: ما هو الخطاف الذي ينطلق فور حذف مقال نهائياً من قاعدة البيانات؟

الخطاف المعماري هو before_delete_post قبل الحذف مباشرة لجمع أي بيانات ضرورية، و deleted_post فور اكتمال عملية مسح السجل من جدول wp_posts.

س14: كيف أحصل على الحالة الحالية لأي منشور برمجياً في PHP؟

باستخدام الدالة الرسمية get_post_status($post_id)، والتي تعود باسم الحالة كنص مثل 'publish' أو 'draft'، أو false إذا لم يكن المنشور موجوداً.

س15: كيف أمنع المقالات ذات الحالة الخاصة من توليد خطأ 404 للمستخدمين غير المسجلين وتحويلهم لصفحة الدخول؟

عبر فحص الاستعلام في خطاف template_redirect؛ إذا كان الاستعلام يطلب مقالاً خاصاً والمستخدم غير مسجل، يمكنك استخدام auth_redirect() لتوجيه الزائر تلقائياً لصفحة wp-login.php مع حفظ رابط العودة.

س16: لماذا تسبب حلقة wp_update_post داخل save_post مشكلة Fatal Error؟

لأن wp_update_post تطلق خطاف save_post مجدداً، مما يدخل السيرفر في حلقة استدعاء متداخلة لا نهائية (Recursion). لمنع ذلك يجب إلغاء الخطاف مؤقتاً عبر remove_action قبل الاستدعاء ثم إعادة ربطه بعد التحديث.

س17: ما الفرق بين الصلاحيتين publish_posts و edit_posts فيما يتعلق بالحالات؟

صلاحية edit_posts تسمح للمستخدم بكتابة وتعديل المسودات وحفظها في حالة draft أو إرسالها لـ pending، ولكنها لا تمنحه حق تحويل المحتوى إلى publish؛ تحويل المحتوى للنشر العام يتطلب حصراً صلاحية publish_posts.

س18: هل تظهر المقالات المجدولة future في خلاصات RSS؟

افتراضياً لا تظهر أبداً في خلاصات الـ RSS للموقع، لأن استعلامات الخلاصة تقتصر حصراً على post_status = 'publish' لضمان عدم تسريب المحتوى قبل موعده الرسمي.

س19: كيف يتعامل ووردبريس مع المقالات في سلة المهملات في واجهة REST API؟

المقالات في السلة لا تعود في طلبات GET العادية لنقاط النهاية، ولكن يمكن للمديرين جلبها عبر تمرير المعامل ?status=trash مع ترويسة المصادقة المعتمدة (Authorized Request).

س20: كيف يمكنني استعادة مقال تم نقله إلى سلة المهملات برمجياً؟

باستخدام الدالة المخصصة في النواة wp_untrash_post($post_id);، والتي تعيد المقال لحالته الأصلية السابقة وتعيد تسمية الـ Slug بإزالة اللاحقة __trashed وتنظف كاش الكائن.

المصادر والمراجع الرسمية

أكمل المسار

الصورة الرمزية لـ Mohamed Abozaid

م. محمد أبو زيد

WordPress Architect & Enterprise Consultant

مهندس برمجيات متخصص في تطوير حلول ووردبريس المتقدمة، معمارية Block Themes، ومحرر Gutenberg، وتحسين الأداء لمواقع الشركات الكبرى في مصر والخليج العربي.


اترك تعليقاً