محرر الصفحات وقف؟ ابحث عن الـ Endpoint المكسور

محرر الصفحات وقف؟ ابحث عن الـ Endpoint المكسور

الموقف ده بيحصل كتير: فعّلت إضافة حماية الأسبوع اللي فات، وانهاردة محرر الصفحات فضل يلف على علامة التحميل، أو حفظ وقالك “فشل” من غير ما يقولك فشل في إيه. وبتفتح ووردبريس لتضيف له ميزة جديدة، مثل متجر أو نموذج تواصل أو تحسين للسيو، دون كتابة كود. اختر الإضافات الموثوقة المحدّثة، ولا…">الإضافات والقوالب والمستخدمين والإعدادات، وما يظهر فيها يختلف حسب دور المستخدم.">لوحة التحكم تدوّر على السبب فما تلاقيش حاجة — طبيعي، لأن لوحة التحكم مش هي اللي هتقولك أنهي طلب توقف.

القصة كلها في ثلاثة مسارات. والخبر الحلو إنك تقدر تعرف أنهي واحد فيهم اتكسر في دقيقة واحدة، من غير ما تحذف ولا إضافة.

المسارات الثلاثة التي يقوم عليها أي محرر صفحات

كل منشئ صفحات (Page Builder) — القالب كاملًا والنماذج والنوافذ المنبثقة.">Elementor أو غيره — يستخدم هذه المسارات بهذا الترتيب:

  1. /wp-admin/ — لوحة التحكم نفسها.
  2. admin-ajax.php — قناة الطلبات المتكررة أثناء التحرير.
  3. /wp-json/ — أي API في ووردبريس واجهة مدمجة تتيح قراءة المحتوى وإنشاءه وتعديله عبر روابط HTTP تحت /wp-json/، دون فتح لوحة التحكم. عليها تعتمد تطبيقات الموبايل والمواقع المنفصلة…">REST API، ومنه يجري الحفظ غالباً.

أي إضافة (Plugin) حماية تغيّر مسارات الموقع قادرة على تحريك أي واحد منها. وحين يتحرك المسار دون أن يوجّه السيرفر العنوان الجديد إلى المعالج الصحيح، يتعطل المحرر بينما يظل الموقع العام يعمل بشكل سليم — وهذا بالضبط سبب حيرة أغلب الناس.

تنبيه: كل ما يلي يُنفَّذ على موقع تملكه أنت ولديك وصول Shell إليه.

افحص الثلاثة من خارج الجلسة

لا تحتاج كوكيز تسجيل دخول في الجولة الأولى. المسارات الثلاثة تجيب على الطلب المجهول بشكل مميز، وشكل الإجابة وحده يخبرك إن كان المسار ما زال يعمل:

SITE="https://example.com"


# 1. wp-admin: an anonymous GET should 302 to the login form.
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}n' "$SITE/wp-admin/"


# 2. admin-ajax.php: no action parameter, so WordPress answers "0" with a 400.
#    The status is the point. A 404 here means the path is gone.
curl -s -o /dev/null -w '%{http_code}n' "$SITE/wp-admin/admin-ajax.php"


# 3. REST API index: an anonymous GET returns the route index as JSON.
curl -s -o /dev/null -w '%{http_code}n' "$SITE/wp-json/"

اقرأ النتيجة هكذا: رمز 302 من الأول و400 من الثاني و200 من الثالث يعني أن المسارات الثلاثة سليمة، وأن مشكلتك في مكان آخر — الكاش على الأرجح، وستجده في قسم ajaxurl أدناه.

أي 404 في هذه المجموعة هو إجابتك مباشرة: هذا المسار تغيّر والسيرفر لا يوجّه العنوان الجديد إلى المعالج القديم.

أما 403 فحيوان مختلف تماماً. معناه أن قاعدة طابقت الطلب، لا أن المسار تحرّك. مجموعة قواعد جدار حماية تعمل على طبقة إعادة الكتابة تُرجع 403 عند تطابق نمط، وحمولة الحفظ القادمة من المحرر قد تُشعل قاعدة كُتبت أصلاً لاصطياد حقن SQL. لو حصلت على 403 من admin-ajax.php و200 من /wp-json/، راجع قواعد الحماية قبل أن تراجع المسارات.

فرّق بين 404 من السيرفر و404 من ووردبريس

هذا التمييز يحدد المكان الذي ستبحث فيه، وهو قابل للقياس فعلياً.

خطأ 404 على مستوى إعادة الكتابة يرجعه Apache أو LiteSpeed أو nginx قبل تشغيل PHP أصلاً. أما 404 من ووردبريس فهو تحميل صفحة كامل: إقلاع، اتصال بقاعدة البيانات، وقالب. الثاني يكلف وقتاً أكبر بمراتب.

# Compare a known static asset, a known WordPress 404, and the endpoint under test.
for U in "/favicon.ico" "/this-page-does-not-exist-9182" "/wp-admin/admin-ajax.php"; do
  printf '%-42s' "$U"
  curl -s -o /dev/null -w '%{http_code}  ttfb=%{time_starttransfer}s  size=%{size_download}n' "$SITE$U"
done

استجابة زمنها قريب من الملف الثابت وحجمها ضئيل لم تصل إلى PHP إطلاقاً، وعلاجها في قواعد إعادة الكتابة. واستجابة زمنها قريب من صفحة 404 وبجسم HTML كامل تعني أن PHP عمل وأن ووردبريس نفسه لم يتعرّف على الطلب — وهذا عادةً فلتر يعيد كتابة الرابط في المخرجات دون قاعدة مقابلة على السيرفر.

ويمكنك التأكد من الترويسات أيضاً، فووردبريس يضيف X-Robots-Tag وترويسة Link في تحميل الصفحات الحقيقي، ولن تجدهما في 404 صادر من السيرفر:

curl -sI "$SITE/wp-admin/admin-ajax.php" | grep -Ei 'server|link|x-robots|x-powered-by'

فخ nginx

إن كان موقعك يعمل على nginx وتعطل المحرر فور تغيير المسارات، فهذا هو السبب أكثر من كل الأسباب الأخرى مجتمعة.

إضافات هذه الفئة تكتب قواعد إعادة التوجيه في ملف .htaccess. وnginx لا يقرأ .htaccess، ولم يفعل يوماً، ولا توجد وحدة تجعله يقرأه. الملف موجود على القرص ويُتجاهَل، بينما تخبرك واجهة الإضافة أن القواعد كُتبت. الإضافة لا تكذب — هي كتبت الملف فعلاً؛ السيرفر هو الذي لا يقرأه.

# On the server. If this returns rules and you are on nginx, they are doing nothing.
grep -n -A20 'BEGIN' /var/www/example.com/public_html/.htaccess


# What is actually serving you:
curl -sI "$SITE" | grep -i '^server:'

القواعد يجب أن تنتقل إلى الـ server block ثم تُعاد تحميل الخدمة. الشكل العام، لإضافة نقلت مساراً وأعطتك الخريطة:

location ~* ^/your-new-ajax-path/?$ {
    rewrite ^ /wp-admin/admin-ajax.php last;
}


# and for a relocated REST namespace
location ~* ^/your-new-api-path/ {
    rewrite ^/your-new-api-path/(.*)$ /wp-json/$1 last;
}
sudo nginx -t && sudo systemctl reload nginx

اختبر الإعدادات قبل إعادة التحميل في كل مرة بلا استثناء. الأمر nginx -t هو الفرق بين إعادة تحميل ناجحة وانقطاع كامل للموقع.

متغيّر ajaxurl القديم الذي لا يفتّش عنه أحد

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

ووردبريس يطبع عنوان AJAX داخل الصفحة كمتغيّر JavaScript، ومنشئات الصفحات تقرأه من هناك بدل أن تبنيه بنفسها. فإذا التقط التخزين المؤقت (Cache) الكامل نسخة HTML قبل تغيير المسار، أو دمج مجمِّع ملفات JavaScript القيمة القديمة داخل حزمة، فالمتصفح يرسل طلبات المحرر إلى عنوان لم يعد موجوداً، بينما السيرفر مستعد تماماً لتوجيه العنوان الجديد.

# What the served HTML currently tells the browser the AJAX endpoint is:
curl -s "$SITE" | grep -oE '(var )?ajaxurl[^;]{0,120};' | head


# And what your builder's own localised config carries:
curl -s "$SITE" | grep -oE '"ajaxurl":"[^"]+"' | head

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

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

curl -s "$SITE" | grep -oE 'src="[^"]+.js[^"]*"' | head -20

خذ نسخة أولاً، ثم أرجِع خطوة بخطوة

خذ نقطة الاسترجاع قبل أن تغيّر أي شيء آخر. أمران فقط، ويحوّلان كل خطوة تالية من مخاطرة إلى تجربة:

cd /var/www/example.com/public_html
cp .htaccess ".htaccess.$(date +%F-%H%M)"
wp db export "pre-path-change-$(date +%F-%H%M).sql" --add-drop-table

ثم أرجِع مساراً واحداً في كل مرة، بهذا الترتيب، مع تجربة حفظ حقيقي في المحرر بين كل خطوة والتي تليها:

  1. مسار AJAX. أغلب أعطال المحررات هنا، لأن المحرر يتحدث إليه باستمرار ولأن العنوان مطبوع في المخرجات لا مُستخرَج وقت الطلب.
  2. مسار REST API. الحفظ هو ما ينكسر، وغالباً ينكسر بصمت، فهو الثاني احتمالاً والأكثر إزعاجاً في التشخيص.
  3. مسار لوحة التحكم. على الاستضافات التي تقيّد التعامل مع مجلد الإدارة، يفشل هذا بشكل يبدو كمشكلة صلاحيات.

ولمعرفة أين تحفظ إضافتك هذه الإعدادات بدل تخمين أسماء الخيارات:

# Substitute your plugin's own option prefix for PREFIX. `wp plugin list` will remind you of the slug.
wp plugin list --status=active --field=name
wp option list --search='*PREFIX*' --fields=option_name,option_value | head -40

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

القاعدة التي تمنع هذا كله

الزيارات المجهولة لا تلمس هذه المسارات الثلاثة أصلاً. الفحص الآلي يستهدف القالب…">الواجهة الأمامية: مجلدات الإضافات والقوالب، ملفات readme، نموذج الدخول، وأرقام الإصدارات في كود الصفحة. تغيير هذه يكسر خطوة الاستطلاع في الهجوم الآلي فعلاً. أما تغيير admin-ajax.php فيكسر محررك ولا يضيف شيئاً يُذكر، لأن لا شيء معادٍ كان ينظر هناك.

بصراحة: ده مش موقف وسط ولا “حل بالراحة”. حسب تقرير Patchstack عن حالة أمان ووردبريس في 2026، 91% من 11,334 ثغرة أُعلنت في منظومة ووردبريس خلال 2025 كانت في الإضافات، و46% منها لم يكن لها تحديث وقت الإعلان، والوسيط المرجّح للزمن بين الإعلان وأول استغلال كان خمس ساعات. تقليل ما هو قابل للوصول إليه هو الطبقة الوحيدة التي تفعل شيئاً داخل نافذة خمس ساعات — ومسار AJAX مخصص ليس جزءاً من هذه الطبقة.

وفي نفس التقرير، اختبار اختراق لدفاعات الاستضافة — جدران حماية داخلية وCloudflare وImunify360 وModSecurity — أوقف 12% من الهجمات على ثغرات معروفة الاستغلال، و26% في اختبار أوسع. اعتبرها تقديراً معقولاً لما تساهم به الطبقة التي تحتك.

الخلاصة العملية: إعداد الحماية الذي يُلغى في يوليو لأنه كسر المحرر لا يحمي شيئاً في أغسطس. الإعداد الذي تتركه في مكانه جزء من تكوين الأمان لا خصم منه.

الخلاصة

قبل ما تحذف إضافة الحماية أو ترجع نسخة احتياطية، شغّل أوامر الـ curl الثلاثة. رمز الاستجابة هيقولك الحكاية في ثانية.

اختبر المسارات الثلاثة أولاً، وفرّق بين 404 السيرفر و404 ووردبريس بالزمن وحجم الجسم، ثم افحص ajaxurl في HTML المخدوم قبل أن تشك في أي شيء آخر. ولو المشكلة عندك أعقد من كده، احجز جلسة من هنا احجز استشارة مجانية ونشخّصها سوا.

مرجع المقال (بالإنجليزية): Your Page Builder Did Not Break. One of Three Endpoints Stopped Resolving.
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.

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

لماذا يعلق محرر Elementor على علامة التحميل بلا رسالة خطأ؟

في أغلب الحالات لأن الطلب لم يصل إلى PHP أصلاً، فلا يوجد ما يُسجَّل في سجل الأخطاء. المحرر يطلب admin-ajax.php أو /wp-json/، وإحدى إضافات الحماية غيّرت المسار دون أن يوجّه السيرفر العنوان الجديد إلى المعالج القديم.

ما الفرق بين خطأ 404 و403 عند اختبار هذه المسارات؟

رمز 404 يعني أن المسار نفسه لم يعد موجوداً — أي أنه تغيّر. أما 403 فيعني أن قاعدة حماية طابقت الطلب ورفضته، والمسار سليم. الأول تعالجه في قواعد إعادة التوجيه، والثاني في إعدادات جدار الحماية.

لماذا لا تعمل قواعد إضافة الحماية على سيرفر nginx؟

لأن هذه الإضافات تكتب قواعدها في ملف .htaccess، وnginx لا يقرأ هذا الملف ولم يفعل يوماً ولا توجد وحدة تجعله يقرأه. الإضافة لا تكذب حين تقول إنها كتبت القواعد؛ السيرفر ببساطة يتجاهل الملف، والحل نقل القواعد إلى الـ server block ثم إعادة تحميل الخدمة.

اختبرت المسارات الثلاثة وكلها سليمة والمحرر ما زال معطلاً — ماذا الآن؟

على الأرجح متغيّر ajaxurl القديم محفوظ داخل كاش الصفحات أو مدمج داخل ملف JavaScript مجمّع. افحص ما يصل للمتصفح فعلياً، ثم امسح الكاش بالترتيب: CDN ثم كاش السيرفر ثم إضافة الكاش ثم المتصفح، وأعد توليد الملفات المدمجة لا مجرد حذفها.

هل تغيير مسار admin-ajax.php يزيد أمان الموقع؟

مكسبه الأمني ضئيل جداً لأن الفحص الآلي لا يستهدف هذه المسارات أصلاً، بينما كلفته تعطيل محرر الصفحات. الأجدى تغيير ما يراه الزائر غير المسجّل — مسارات الإضافات والقوالب وصفحة الدخول وأرقام الإصدارات — وترك المسارات الثلاثة التي تعتمد عليها أدواتك في مكانها.

اترك تعليقاً