الأدلة التقنية

Moodle: منصة تعلم بالعربية على سيرفرك

كيف تثبت Moodle على سيرفرك وتجهزه بالعربية، ثم تنشئ مقرراً واختباراً وتسجل الطلاب وتأخذ له نسخة احتياطية كاملة.

Moodle: منصة تعلم بالعربية على سيرفرك

في مقال أنظمة إدارة التعلم LMS وصلنا إلى ما تحتاجه المدرسة أو الجامعة من منصة التعلم، وإلى أن Moodle هو الأوسع انتشاراً بين الخيارات المفتوحة، والسؤال الآن عملي: كيف تشغله على سيرفرك، وكيف يبدو بالعربية فعلاً، وما الذي تفعله في أول يوم بعد التثبيت؟ وهذا هو المقال الثاني في مرحلة التعليم الإلكتروني للمدارس والجامعات، وسوف نناقش فيه ما يلي:

  • متطلبات Moodle 5.3 كما في ملاحظات الإصدار، ومختبر كامل على Docker: سيرفر الويب وPostgreSQL وContainer مستقل للمهام المجدولة Cron.
  • التثبيت من سطر الأوامر باللغة العربية، وإعداد الموجه Router الذي يطلبه Moodle منذ الإصدار 5.1، ونكسر الإعداد عمداً ونقرأ الخطأ.
  • العربية واتجاه الكتابة RTL بصراحة: ما الذي يعمل جيداً، وما الذي وجدناه غير مترجم أو مختلطاً.
  • إنشاء مقرر وتسجيل معلم وطلاب من ملفات CSV، ثم اختبار حقيقي يحله طالب، والدرجة في سجل الدرجات.
  • الدخول الموحد عبر OpenID Connect، والنسخ الاحتياطي، والترقية Upgrade، وكيف يكبر Moodle مع عدد الطلاب.

ما الذي يحتاجه Moodle 5.3؟

ملاحظات إصدار Moodle 5.3، الصادر في 5 أكتوبر 2026، تحدد المتطلبات التالية، وهي الحد الأدنى وليس الموصى به:

  • PHP: الإصدار 8.3.0 على الأقل، ويدعم 8.4 أيضاً، ونسخ 64 بت فقط، مع إضافة sodium، وإعداد max_input_vars بقيمة 5000 أو أكثر.
  • قاعدة البيانات Database: PostgreSQL 17، أو MySQL 8.4، أو MariaDB 11.4، وقد رفع هذا الإصدار الحد الأدنى لكل من PostgreSQL وMariaDB.
  • الترقية Upgrade: لا يمكن الترقية إلى 5.3 إلا من Moodle 4.4 أو أحدث.
  • الدعم: هو إصدار دعم طويل LTS، وجدول الإصدارات يذكر أن إصلاح الأخطاء العامة فيه يستمر حتى 4 أكتوبر 2027، والإصلاحات الأمنية حتى 1 أكتوبر 2029.

وأما العتاد فدليل التثبيت الرسمي يذكر 512MB من الذاكرة كحد أدنى، وينصح بـ 1GB أو أكثر، ويقول إن 8GB وما فوقها متوقعة في سيرفر إنتاج كبير، و5GB من القرص كحد أدنى واقعي، ولكن تذكر ما ذكرناه في المقال السابق: الحجم الحقيقي تحدده دقيقة بداية الاختبار، وسوف نعود إليه في آخر المقال.

التجربة العملية: Moodle على Docker

لا يوجد Docker Image رسمي جاهز لتشغيل Moodle في الإنتاج، والذي ينشره فريق Moodle نفسه هو moodle-php-apache، وهو Apache مع PHP وكل الإضافات التي يحتاجها Moodle، ولكن دون كود Moodle نفسه، وملف المشروع يصفه بأنه بيئة مهيأة لتطوير Moodle. لذلك سوف نستخدمه في المختبر، ونضع فيه كود Moodle من الإصدار 5.3.0 بأنفسنا، وفي الإنتاج يمكنك أن تبني Docker Image خاصاً بك فوقه فيه الكود، أو أن تثبت PHP وApache على السيرفر مباشرة كما في دليل التثبيت الرسمي. والصورة التالية تبين المختبر الذي سوف نبنيه:

مخطط مختبر Moodle داخل Docker Compose باسم lab-lms: المتصفح على 127.0.0.1:18480 يصل إلى lab-lms-web وفيه Apache وPHP 8.3 ومجلد public، وlab-lms-cron من الصورة نفسها يشغل cron.php كل دقيقة، وكلاهما يتصل بقاعدة lab-lms-db وفيها PostgreSQL 17.11، وأسهم متقطعة خضراء إلى ثلاثة Volumes هي كود Moodle 5.3.0 وmoodledata وملفات PostgreSQL
ثلاث Containers وثلاثة Volumes، وال Container الوحيد الذي له منفذ منشور هو سيرفر الويب

ملفات المختبر

أنشئ مجلداً للمختبر، وضع فيه الملف compose.yaml التالي:

name: lab-lms
services:
  db:
    image: postgres:17.11
    container_name: lab-lms-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: moodle
      POSTGRES_USER: moodle
      POSTGRES_PASSWORD: change-me-db
    volumes:
      - db:/var/lib/postgresql/data

  web:
    image: moodlehq/moodle-php-apache:8.3-bookworm@sha256:501f19fa75ad3cdc8962f2a742f39d179b8aa8ffdc2ff233d8d27a32f14e0e05
    container_name: lab-lms-web
    restart: unless-stopped
    depends_on: [db]
    environment:
      PHP_INI-max_input_vars: "5000"
      PHP_INI-display_errors: "Off"
      PHP_INI-upload_max_filesize: 100M
      PHP_INI-post_max_size: 110M
    ports:
      - "127.0.0.1:18480:80"
    volumes:
      - code:/var/www/html
      - data:/var/www/moodledata
      - ./moodle-router.conf:/etc/apache2/conf-enabled/moodle-router.conf:ro

  cron:
    image: moodlehq/moodle-php-apache:8.3-bookworm@sha256:501f19fa75ad3cdc8962f2a742f39d179b8aa8ffdc2ff233d8d27a32f14e0e05
    container_name: lab-lms-cron
    restart: unless-stopped
    depends_on: [db]
    user: www-data
    entrypoint: ["sh", "-c", "while true; do php /var/www/html/admin/cli/cron.php --keep-alive=0 >/proc/1/fd/1 2>&1; sleep 60; done"]
    environment:
      PHP_INI-max_input_vars: "5000"
    volumes:
      - code:/var/www/html
      - data:/var/www/moodledata

volumes:
  db:
    name: lab-lms-db
  code:
    name: lab-lms-code
  data:
    name: lab-lms-data

ثم الملف moodle-router.conf الذي سوف نشرح سببه بعد قليل:

# Moodle 5.1+ router: unknown paths under public/ go to r.php
<Directory /var/www/html/public>
    AllowOverride None
    DirectoryIndex index.php
    FallbackResource /r.php
</Directory>

في الإعداد أعلاه لاحظ التالي:

  • تثبيت الإصدار Pinning: الوسم 8.3-bookworm يتحرك مع كل بناء جديد للصورة، لذلك ثبتناه بالبصمة @sha256 التي أجرينا عليها المختبر، فيبقى المختبر كما هو بعد شهر، وفي postgres:17.11 رقم الإصدار نفسه ثابت.
  • المنفذ Port: سيرفر الويب منشور على 127.0.0.1 فقط، فلا يصل إليه أحد من الشبكة، وفي الإنتاج يكون أمامه Reverse Proxy بشهادة HTTPS كما في تثبيت Caddy، وقاعدة البيانات ليس لها منفذ منشور أصلاً.
  • ال Cron في Container مستقل: يستخدم الصورة نفسها والكود نفسه والبيانات نفسها، ووظيفته الوحيدة أن يشغل admin/cli/cron.php كل دقيقة، وتوثيق ال Cron يوصي بتشغيله كل دقيقة، لأن مهام كثيرة مثل إرسال البريد وحذف الأنشطة من سلة المحذوفات تعتمد عليه.
  • مجلد البيانات moodledata: في Volume منفصل خارج مجلد الويب، ودليل التثبيت يحذر من وضعه داخل مكان يصل إليه المتصفح، لأنه خطر أمني كبير.
  • display_errors: أوقفناه لأن الصورة مهيأة للتطوير، وفحص الأمان في Moodle ينبه إليه كما سيأتي.

وضع كود Moodle في ال Volume

حمل الإصدار 5.3.0 من مستودع Moodle باسم moodle.tgz في مجلد المختبر، ثم افرده داخل ال Volume بال Container نفسه، حتى تكون الملفات مملوكة للمستخدم www-data الذي يشغل Apache:

curl -sL -o moodle.tgz https://github.com/moodle/moodle/archive/refs/tags/v5.3.0.tar.gz
docker volume create lab-lms-code
docker run --rm --name lab-lms-unpack -v "$PWD":/src:ro -v lab-lms-code:/var/www/html \
  --entrypoint sh moodlehq/moodle-php-apache:8.3-bookworm \
  -c 'tar xzf /src/moodle.tgz -C /var/www/html --strip-components=1 && chown -R www-data:www-data /var/www/html'
docker compose up -d

وقد يتساءل البعض: لماذا لا نربط مجلداً من السيرفر مباشرة Bind Mount؟ والإجابة أن ذلك ممكن على سيرفر Linux، ولكن كود Moodle فيه عشرات آلاف الملفات الصغيرة، وحجمه بعد الفرد 629MB في تجربتنا، ووضعه في Volume يجعل الملكية والصلاحيات واضحة من البداية. ولاحظ أن Moodle منذ الإصدار 5.1 نقل الملفات التي يصل إليها المتصفح إلى مجلد فرعي اسمه public، والصورة تكتشف ذلك وتجعل المجلد الرئيسي للموقع Document Root هو /var/www/html/public تلقائياً، بينما بقيت سكربتات سطر الأوامر مثل admin/cli/install.php خارجه.

التثبيت من سطر الأوامر CLI بالعربية

بدلاً من معالج التثبيت في المتصفح، نستخدم السكربت install.php بالخيار --lang=ar، فيحمل حزمة اللغة العربية ويجعلها لغة الموقع الافتراضية:

docker exec -u www-data lab-lms-web php /var/www/html/admin/cli/install.php \
  --non-interactive --agree-license --lang=ar \
  --wwwroot=http://127.0.0.1:18480 --dataroot=/var/www/moodledata \
  --dbtype=pgsql --dbhost=db --dbname=moodle --dbuser=moodle --dbpass=change-me-db \
  --fullname="Lab School" --shortname=labschool \
  --adminuser=admin --adminpass='Lab-Admin-2026!' [email protected] \
  [email protected] > install.log 2>&1

والمخرج سوف يكون كما يلي، وهو أكثر من 800 سطر، وهذه بدايته ونهايته:

برنامج تنصيب مودل 5.3 (Build: 20261005) عبر سطر الأوامر النصية
-->النظام
++ install.xml: نجاح (1.66 ثوان) ++
++ xmldb_main_install: نجاح (7.26 ثوان) ++
++ external_update_descriptions: نجاح (2.62 ثوان) ++
++ admin_apply_default_settings: نجاح (43.78 ثوان) ++
++ نجاح (56.30 ثوان) ++
-->aiplacement_courseassist
++ نجاح (0.08 ثوان) ++
...
-->factor_webauthn
++ نجاح (0.06 ثوان) ++
-->upgrade_noncore()
++ external_update_services: نجاح (1.42 ثوان) ++
++ نجاح (1.55 ثوان) ++
تم التنصيب بنجاح.

في المخرج أعلاه لاحظ التالي:

  • كل سطر يبدأ بـ --> هو مكون Component يثبت في قاعدة البيانات، وفي تجربتنا كان عددها 413 مكوناً، وPostgreSQL فيها بعد التثبيت 499 جدولاً.
  • لاحظ أن أول المكونات بعد النظام هي مكونات الذكاء الاصطناعي، فالإصدار يأتي بموصلات لعدة مزودين، ولا يعمل أي منها دون حساب ومفتاح من المزود يضبطه المدير، وهي نقطة تستحق أن تقرر فيها المؤسسة سياسة واضحة قبل أن يفعلها أحد.
  • أرسل المخرج إلى ملف كما في الأمر أعلاه ولا تمرره إلى head، والسبب أن الأمر head في أول محاولة لنا أوقف العرض بعد أربعين سطراً والتثبيت ما زال يعمل في الخلفية، فظننا أنه انتهى، ولو بدأت محاولة ثانية في هذه اللحظة لتداخلت المحاولتان على قاعدة البيانات نفسها.

نكسر الإعداد: الموجه Router

بعد التثبيت مباشرة، وقبل أن نضيف ملف moodle-router.conf، شغلنا فحوص النظام من سطر الأوامر:

docker exec -u www-data lab-lms-web php /var/www/html/admin/cli/checks.php

والمخرج كان كما يلي:

CRITICAL: تهيئة الموجه (core_router)
الحالة | تحقق
----------+--------------------------------------------------------------------
    ERROR | تهيئة الموجه (core_router)
          |     إن الموجه غير مهيأ.
          |     إن الموجه غير مهيأ بشكل صحيح. يرجى الرجوع إلى التوثيقات لمزيد من المعلومات عن كيفية تهيئته.

والسبب أن Moodle أدخل بنية التوجيه Routing في الإصدار 5.1، وأصبح يحتاج من سيرفر الويب أن يرسل كل مسار لا يقابله ملف حقيقي إلى السكربت r.php، وصفحة إعداد الموجه تعطي سطر Apache المطلوب، وهو FallbackResource /r.php، وتطلب إضافة السطر التالي إلى config.php قبل سطر require_once:

$CFG->routerconfigured = true;

بعد إضافة الملف والسطر وإعادة تشغيل سيرفر الويب، طلبنا مساراً من مسارات الموجه من خارج ال Container:

curl -s -w ' %{http_code}\n' http://127.0.0.1:18480/core/check/controller/test
Test controller response 200

ولكن عند تشغيل الفحص مرة أخرى بقي الخطأ برسالة مختلفة:

    ERROR | تهيئة الموجه (core_router)
          |     لم يرجع الرابط الآتي رمز الحالة المتوقع: http://127.0.0.1:18480/core/check/controller/test. المتوقع هو 200 ("OK") وما تم تلقيه هو 0 ("cURL error 7: Failed to connect to 127.0.0.1 port 18480 after 0 ms: Couldn't connect to server

وهذا مثال جيد على قراءة الخطأ قبل إصلاحه، فالفحص يطلب عنوان الموقع wwwroot من داخل ال Container نفسه، والعنوان 127.0.0.1:18480 موجود على جهازنا فقط وليس داخل ال Container، لذلك يفشل الاتصال ورمز الحالة 0، والموجه نفسه يعمل كما رأينا. وفي الإنتاج يكون wwwroot نطاقاً حقيقياً مثل https://lms.example.com يصل إليه السيرفر، فيختفي هذا الخطأ، والقاعدة هنا: تأكد أن السيرفر يستطيع أن يصل إلى عنوانه العام، لأن Moodle يستخدم ذلك في فحوصه وفي بعض المهام.

هل يعمل ال Cron؟

docker logs --tail 4 lab-lms-cron
Cron run completed correctly
Cron completed at 22:03:39 in 0.581148 seconds. Memory used: 30.1 &nbsp;ميغا بايت.
Cron run completed correctly
Cron completed at 22:04:40 in 0.595744 seconds. Memory used: 29.4 &nbsp;ميغا بايت.

الفاصل بين التشغيلين دقيقة كما أردنا، وفحص النظام أظهر الحالة OK للفحص tool_task_cronrunning. ولاحظ النص &nbsp; قبل كلمة ميغا بايت، وهو مسافة HTML مكتوبة داخل الترجمة العربية فتظهر حرفياً في سطر الأوامر، وهو أول مثال من الأمثلة التي في القسم التالي.

العربية واتجاه الكتابة RTL بصراحة

حزمة اللغة العربية لإصدار 5.3 مترجمة بنسبة 99% بحسب صفحة حزم اللغة، أي 32610 نصاً من 32811، وفي ملف إعدادها thisdirection بقيمة rtl، فتنقلب الواجهة كلها إلى اليمين، والقوائم والجداول والأزرار تعمل كما ينبغي في كل الصفحات التي فتحناها. والصورة التالية لصفحة أسئلة الاختبار الذي أنشأناه:

صفحة أسئلة اختبار في Moodle بالعربية من اليمين إلى اليسار: عنوان اختبار الوحدة الأولى أساسيات الشبكات، والدرجة القصوى 10.00، وثلاثة أسئلة في ثلاث صفحات، الأول وظيفة DNS والثاني وظيفة DHCP والثالث المنفذ الافتراضي لـ HTTPS، وبجانب كل سؤال درجته 1.00، وفي القائمة الجانبية أقسام المقرر بعنوان قسم جديد
صفحة أسئلة الاختبار بالعربية، والقائمة الجانبية على اليمين كما ينبغي

ولكن أثناء التجربة وجدنا التفاصيل التالية، وهي التي سوف يسألك عنها المعلمون:

  • الوقت بحروف إنجليزية: صفحة نتيجة الاختبار تعرض «الخميس، 8 أكتوبر 2026، 9:17 PM»، أي التاريخ بالعربية واختصار PM بالإنجليزية، وفي ملف إعداد الحزمة العربية القيمة localecldr هي en-AU، وهي على الأرجح مصدر هذا الخلط.
  • ترقيم الخيارات: خيارات سؤال الاختيار من متعدد ظهرت بالحروف a وb وc وd، ويمكن للمعلم تغيير نوع الترقيم من إعدادات كل سؤال.
  • نص غير مكتمل: أداة رفع المقررات من سطر الأوامر طبعت «مجموع المقررات الدراسية: {$ }»، أي أن المتغير الذي يحمل الرقم سقط من الترجمة.
  • مصطلحان لشيء واحد: السطر نفسه قال «تم إنشاء مساق» ثم «المقررات الدراسية التي تم إنشاؤها»، فالكلمة Course مترجمة مرة «مساق» ومرة «مقرر»، ويمكن توحيدها من أداة تخصيص اللغة Language Customisation في إدارة الموقع.
  • اسم ملف النسخة الاحتياطية بالعربية: كما سيأتي في قسم النسخ الاحتياطي، وهذا يهم من يكتب سكربتات تتعامل مع هذه الملفات.
  • أسماء الأقسام الافتراضية: المقرر الجديد أقسامه كلها بعنوان «قسم جديد»، فعلى المعلم تسميتها بنفسه، وهذا ليس خطأ ترجمة ولكنه أول ما يلاحظه المعلم.

والصورة التالية لنتيجة محاولة الطالب، وفيها الوقت بالخلط الذي ذكرناه وترقيم الخيارات بالحروف الإنجليزية:

صفحة مراجعة محاولة الطالب خالد في Moodle بالعربية: رسالة المحاولة تم تسليمها، والحالة منتهية، وبدأ الخميس 8 أكتوبر 2026 9:17 PM، واكتمل 9:18 PM، والفترة 12 ثانية، والعلامات 1.00 من 3.00، والدرجة 3.33 من 10.00 بنسبة 33.33%، ثم السؤال الأول عن وظيفة DNS وخياراته مرقمة a وb وc وd والإجابة الصحيحة تحويل اسم النطاق إلى عنوان IP عليها علامة صح
نتيجة محاولة الطالب: الواجهة بالعربية، ولكن اختصار PM وترقيم الخيارات بالحروف الإنجليزية

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

مقرر ومعلم وطلاب من ملفات CSV

الطريقة البسيطة هي أن تنشئ المقرر من الواجهة وتضيف الطلاب واحداً واحداً، وهذا مقبول لمقرر تجريبي، ولكنه لا يصلح لجامعة فيها مئات الشعب، لذلك يأتي Moodle بأداتين في سطر الأوامر لرفع المقررات والمستخدمين من ملفات CSV، وهما الأداتان نفسهما اللتان تستخدمهما من الواجهة. أنشئ الملف courses.csv:

shortname,fullname,category,format,numsections,lang
NET101,مقدمة في الشبكات,1,topics,4,ar

ثم ارفعه:

docker cp courses.csv lab-lms-web:/tmp/courses.csv
docker exec -u www-data lab-lms-web php /var/www/html/public/admin/tool/uploadcourse/cli/uploadcourse.php \
  --mode=createnew --updatemode=nothing --file=/tmp/courses.csv

والمخرج سوف يكون كما يلي:

Moodle course uploader running ...
line	result	id	shortname	fullname	idnumber
1	OK	2	NET101	مقدمة في الشبكات
  تم إنشاء مساق
مجموع المقررات الدراسية: {$ }
المقررات الدراسية التي تم إنشاؤها: 1
المقررات الدراسية التي تم تحديثها: 0
تم حذف المقررات الدراسية: 0
أخطاء المقررات الدراسية: 0

ثم ملف المستخدمين users.csv، وفيه معلمة وثلاثة طلاب، والعمودان course1 وrole1 يسجلان كل مستخدم في المقرر بدوره في الخطوة نفسها:

username,password,firstname,lastname,email,lang,course1,role1
teacher1,Lab-Teach-2026!,سارة,المعلمة,[email protected],ar,NET101,editingteacher
student1,Lab-Stud-2026!,خالد,الطالب,[email protected],ar,NET101,student
student2,Lab-Stud-2026!,ريم,الطالبة,[email protected],ar,NET101,student
student3,Lab-Stud-2026!,عمر,الطالب,[email protected],ar,NET101,student
docker cp users.csv lab-lms-web:/tmp/users.csv
docker exec -u www-data lab-lms-web php /var/www/html/public/admin/tool/uploaduser/cli/uploaduser.php \
  --file=/tmp/users.csv --uupasswordnew=0 --uuforcepasswordchange=0
السطر 2
  مستخدم جديد
  الانضمامات: منضم إلى "NET101" بدور "editingteacher"
Error: lib/moodlelib.php email_to_user(): Invalid address:  (From): [email protected]
السطر 3
  مستخدم جديد
  الانضمامات: منضم إلى "NET101" بدور "student"
...
-------------------------------------------------------------------------------
تم إنشاء المستخدمين: 4
المستخدمون ذوو كلمة المرور الضعيفة: 0
الأخطاء: 0

في المخرج أعلاه لاحظ التالي:

  • أنشئ المستخدمون الأربعة وسجلوا في المقرر، والأداة لم تحسب سطر Error خطأ في الرفع، لأنه خطأ في إرسال بريد الترحيب وليس في إنشاء الحساب.
  • سبب خطأ البريد أن Moodle بنى عنوان المرسل من اسم السيرفر، فأصبح [email protected]، وهو عنوان غير صالح، وفي الإنتاج اضبط عنوان المرسل noreplyaddress وخادم SMTP من إدارة الموقع، كما شرحنا في Transactional Email، وإلا فلن تصل رسائل استعادة كلمة المرور ولا إشعارات المنتديات.
  • الخيار --uupasswordnew=0 يعني أن كلمة المرور في الملف، وفي الإنتاج مع الدخول الموحد لن تحتاج كلمات مرور في الملف أصلاً.

والصورة التالية لصفحة المشاركين في المقرر كما تراها المعلمة، ولاحظ فيها فلتر الأسماء بالحروف العربية تحت فلتر الحروف الإنجليزية:

صفحة المشاركين في مقرر مقدمة في الشبكات بالعربية: زر ضم المستخدمين، وفلتر الشروط، وتم العثور على 4 من المشتركين، ثم صفان من الحروف لتصفية الاسم الأول، الأول بالحروف الإنجليزية من A إلى Z والثاني بالحروف العربية من ا إلى ي، ومثلهما للاسم الأخير
المشاركون في المقرر: أربعة مستخدمين من ملف CSV، وفلتر الأسماء بالحروف العربية والإنجليزية

اختبار Quiz حقيقي يحله طالب

أنشأنا الاختبار من الواجهة كما يفعل المعلم، بثلاث خطوات:

  1. إضافة النشاط: من صفحة المقرر في وضع التحرير، أضف نشاطاً من نوع اختبار Quiz في القسم الأول، واكتب عنوانه، ونحن سميناه «اختبار الوحدة الأولى: أساسيات الشبكات».
  2. إضافة الأسئلة: من صفحة الأسئلة اختر «أضف» ثم «سؤال جديد»، وأضفنا سؤالين من نوع الاختيار من متعدد Multiple Choice عن DNS وDHCP، وسؤالاً من نوع صح وخطأ True/False عن منفذ HTTPS، وكل سؤال بدرجة واحدة، والدرجة القصوى للاختبار 10.
  3. المحاولة: دخلنا بحساب الطالب خالد، وبدأنا المحاولة، وأجبنا الأول صحيحاً والثاني خطأ، وتركنا الثالث دون إجابة، ثم سلمنا.

والنتيجة كانت كما في الصورة السابقة: 1.00 من 3.00، أي 3.33 من 10.00 بنسبة 33.33%، والفترة 12 ثانية. والصورة التالية لتقرير المقيم Grader Report في سجل الدرجات كما تراه المعلمة:

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

لاحظ أن المتوسط العام 3.33 محسوب من الطالب الوحيد الذي حاول، وليس من الثلاثة، لأن الدرجة الفارغة لا تعد صفراً في الإعداد الافتراضي، فالإعداد grade_report_meanselection قيمته في موقعنا 1، وهي الثابت GRADE_REPORT_MEAN_GRADED في الكود، أي متوسط الدرجات غير الفارغة فقط، وهذا إعداد يجب أن تتفق عليه المؤسسة مع معلميها قبل أول فصل، لأنه يغير المتوسطات في التقارير.

الدخول الموحد SSO عبر OpenID Connect

يأتي Moodle بإضافة مصادقة OAuth 2 ضمن الكود الأساسي، وتعمل مع أي مزود هوية يدعم OpenID Connect، مثل authentik الذي شرحنا تثبيته في تثبيت authentik للدخول الموحد. وتوثيق خدمات OAuth 2 يذكر أن المزود إذا كان متوافقاً مع OpenID Connect فيكفي أن تكتب عنوانه الأساسي، فيكتشف Moodle باقي الإعدادات من المستند /.well-known/openid-configuration، وعنوان الرجوع Redirect URI الذي تكتبه في المزود هو <wwwroot>/admin/oauth2callback.php. والخطوات باختصار:

  1. في authentik، أنشئ مزوداً من نوع OAuth2/OpenID وتطبيقاً له، وعنوان الرجوع https://lms.example.com/admin/oauth2callback.php، واحفظ Client ID وClient Secret.
  2. في Moodle، من إدارة الموقع ثم الخادم ثم خدمات OAuth 2، أنشئ خدمة مخصصة Custom، واكتب عنوان المزود في حقل Issuer URL مع المعرف والسر.
  3. فعل إضافة المصادقة OAuth 2 من إدارة الموقع ثم الإضافات ثم المصادقة، فيظهر زر الدخول بحساب المؤسسة في صفحة الدخول.
  4. اربط حقول المستخدم User Field Mappings، وأهمها البريد واسم المستخدم، حتى يقابل الحساب القادم من authentik الحساب الموجود في Moodle.

وهي الفكرة نفسها التي طبقناها خطوة بخطوة في دخول موحد إلى Gitea بحسابات authentik، وللربط بين authentik وMoodle مقال مستقل قادم في صفحة المرحلة، فيه ربط المجموعات بالأدوار وإنشاء الحسابات تلقائياً.

النسخ الاحتياطي Backup

في Moodle نوعان من النسخ الاحتياطي، ولكل منهما وظيفة مختلفة، والخلط بينهما من أشهر الأخطاء.

نسخة المقرر Course Backup

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

docker exec -u www-data lab-lms-web sh -c 'mkdir -p /tmp/bk; cd /var/www/html; php admin/cli/backup.php --courseid=2 --destination=/tmp/bk'
== Performing backup... ==
Writing /tmp/bk/حفظ_إحتياطي-moodle2-course-2-NET101-20261008-2121.mbz
Backup completed.

لاحظ أن اسم الملف بدأ بكلمة «حفظ_إحتياطي» لأن لغة الموقع العربية، وحجمه 11084 بايت، وهذا لا يضر Moodle، ولكن إذا كان لديك سكربت ينقل النسخ إلى مكان آخر ويبحث عن الملفات بنمط مثل backup-*.mbz فلن يجد شيئاً، فاكتب السكربت على الامتداد .mbz وليس على بداية الاسم.

نسخة الموقع كاملاً Full Backup

نسخ المقررات لا تحمي الموقع، فهي لا تحتوي على المستخدمين كلهم ولا إعدادات الموقع ولا الإضافات، والنسخة الكاملة ثلاثة أشياء معاً: قاعدة البيانات، ومجلد moodledata، والكود مع ملف config.php، ويجب أن تؤخذ الثلاثة في لحظة واحدة، لذلك نضع الموقع في وضع الصيانة أولاً:

docker exec -u www-data lab-lms-web php /var/www/html/admin/cli/maintenance.php --enable
docker exec lab-lms-db sh -c 'pg_dump -U moodle -Fc moodle > /tmp/moodle.dump && ls -la /tmp/moodle.dump'
docker exec lab-lms-web sh -c 'tar czf /tmp/moodledata.tgz -C /var/www moodledata && ls -la /tmp/moodledata.tgz'
docker exec -u www-data lab-lms-web php /var/www/html/admin/cli/maintenance.php --disable
== وضع الصيانة (http://127.0.0.1:18480) ==
موقعك حالياً في وضع الصيانة عبر واجهة سطر الأوامر، لا يُسمح بالوصول إليه عبر الويب.
-rw-r--r-- 1 root root 3578938 Oct  8 20:21 /tmp/moodle.dump
-rw-r--r-- 1 root root 7690307 Oct  8 20:21 /tmp/moodledata.tgz
== وضع الصيانة (http://127.0.0.1:18480) ==
تم تعطيل وضع الصيانة والموقع يعمل بشكل طبيعي مجدداً.

في موقعنا الصغير كانت قاعدة البيانات 40MB في PostgreSQL ونسختها المضغوطة 3.6MB تقريباً، ومجلد moodledata 41MB ونسخته 7.7MB، وفي موقع حقيقي يكبر moodledata بسرعة لأنه يحمل كل ملف يرفعه معلم أو طالب. ثم انقل الملفات خارج السيرفر واختبر استعادتها كما في النسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادة.

⚠️
لا تأخذ النسخة الكاملة في أيام الاختبارات أو في ساعات الذروة، والسبب أن وضع الصيانة يمنع الطلاب من الدخول، وأن ضغط مجلد moodledata الكبير يستهلك القرص والمعالج في الوقت الذي يحتاجهما فيه الطلاب.

الترقية Upgrade

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

  1. تأكد أن الإصدار الذي أنت عليه يسمح بالانتقال، فالإصدار 5.3 يطلب 4.4 أو أحدث، ومن كان على إصدار أقدم يرقي على مراحل.
  2. تأكد أن كل إضافة Plugin مثبتة لها إصدار متوافق مع الإصدار الجديد، فالإضافة التي لا تدعمه توقف الترقية.
  3. خذ نسخة كاملة كما في القسم السابق، وجرب الترقية على نسخة من الموقع أولاً.
  4. ضع الموقع في وضع الصيانة، وضع الكود الجديد في مجلد جديد أو Volume جديد، ثم انقل إليه config.php والإضافات التي ليست من الكود الأساسي.
  5. شغل php admin/cli/upgrade.php --non-interactive، ثم أعد الموقع من وضع الصيانة، وراجع فحوص النظام.

وجدول الإصدارات يذكر أن 5.3.1 مخطط له في 7 ديسمبر 2026، والإصدارات الفرعية مثل هذا تأتي بإصلاحات أمنية، فلا تؤجلها.

كيف يكبر Moodle مع عدد الطلاب؟ التوسع Scaling

في مختبرنا وهو خامل كان سيرفر الويب يستخدم 155MB من الذاكرة، وPostgreSQL يستخدم 105MB، وال Cron نحو 8MB بين تشغيلين، وهذه أرقام لا تقول شيئاً عن يوم الاختبار، ولكنها تبين أن البداية خفيفة. وإرشادات الأداء الرسمية تقول إن أسرع تحسين وأكثره أثراً هو زيادة الذاكرة على سيرفر الويب، وتطلب التأكد من تفعيل OPcache في PHP، وتنصح بشدة بخادم Redis للذاكرة المؤقتة وتفضله على Memcached، وتقول إن المواقع الكبيرة تفصل سيرفر الويب عن قاعدة البيانات بينما لا يحتاج الموقع الصغير ذلك، وتحذر من وضع moodledata على تخزين بطيء مثل NFS من جهاز NAS بالإعدادات الافتراضية للذاكرة المؤقتة. والصورة التالية تبين المراحل الثلاث:

ثلاثة صناديق متتالية: مدرسة صغيرة بسيرفر واحد عليه الويب وPHP وال cron وPostgreSQL وmoodledata على SSD محلي، ثم مرحلة النمو بسيرفر للويب مع OPcache وPostgreSQL على سيرفر مستقل وRedis للذاكرة المؤقتة والجلسات وmoodledata على قرص سريع، ثم موسم الاختبارات بثلاثة سيرفرات ويب خلف موزع حمل وPostgreSQL رئيسي وRedis مشترك وmoodledata مشترك عبر NFS وسيرفر مستقل لل cron
من سيرفر واحد إلى عدة سيرفرات ويب خلف موزع حمل، وكل مرحلة تضيف شيئاً تراقبه وتنسخه

في المخطط أعلاه لاحظ التالي:

  • عندما يصبح للموقع أكثر من سيرفر ويب، يجب أن يكون moodledata مشتركاً بينها، وأن تكون الجلسات Sessions في مكان مشترك مثل Redis، وإلا فسوف يخرج الطالب من الاختبار كلما انتقل طلبه إلى سيرفر آخر.
  • توثيق ال Cron يذكر أن النظام يسمح افتراضياً بثلاث مهام مجدولة Scheduled Tasks وثلاث مهام فورية Ad hoc Tasks في الوقت نفسه، ويمكن رفع الحدين حسب قدرة السيرفر، وتشغيل معالجات مخصصة للمهام الفورية، وهذا ما يجعل سيرفر ال Cron المستقل مفيداً في المواقع الكبيرة.
  • قبل أن تنتقل إلى المرحلة التالية، قس، فاختبار الأحمال الذي شرحناه في اختبار الأحمال والضغط على سيناريو بداية الاختبار يخبرك أين الاختناق، وقد يكون الحل ذاكرة أكبر وليس سيرفراً إضافياً.

أمان الموقع Security

يأتي Moodle بفحوص أمان من سطر الأوامر، وهذا ما أظهرته في مختبرنا قبل أن نوقف display_errors:

docker exec -u www-data lab-lms-web php /var/www/html/admin/cli/checks.php --type=security
CRITICAL: تحقق من كل المسارات العمومية/الخاصة (core_publicpaths)
  WARNING | عرض أخطاء PHP (core_displayerrors)
          |     إعداد PHP لعرض الأخطاء مُمَكَّن.
          |     من الموصى به تعطيله.

التحذير الأول عن display_errors، وسببه أن الصورة مهيأة للتطوير، وقد أوقفناه في ملف Compose، والسبب أن رسائل الخطأ قد تكشف للزائر مسارات ومعلومات عن السيرفر. وأما الخطأ الحرج عن المسارات العامة core_publicpaths فهو من النوع نفسه الذي رأيناه في فحص الموجه، فالفحص يحاول أن يطلب مجلدات مثل vendor/ وnode_modules/ عبر wwwroot ليتأكد أنها غير مكشوفة، ولا يستطيع الوصول إلى العنوان من داخل ال Container، فأعد تشغيله على السيرفر الحقيقي بنطاقه الحقيقي، ولا تتجاهله هناك. وأضف إلى ذلك:

  • الدخول الموحد مع التحقق الثنائي لحسابات المعلمين والمدراء على الأقل.
  • لا تثبت إضافة من مصدر غير معروف، فالإضافة كود PHP يعمل بكل صلاحيات الموقع.
  • تابع الإصدارات الفرعية وطبقها، فهي التي تحمل الإصلاحات الأمنية.

تنظيف المختبر

احذف ال Containers وال Volumes الخاصة بالمختبر فقط، بأسمائها:

docker compose down -v
 Container lab-lms-cron Stopping
 Container lab-lms-web Stopping
 Container lab-lms-web Stopped
 Container lab-lms-web Removing
 Container lab-lms-web Removed
 Container lab-lms-cron Stopped
 Container lab-lms-cron Removing
 Container lab-lms-cron Removed
 Container lab-lms-db Stopping
 Container lab-lms-db Stopped
 Container lab-lms-db Removing
 Container lab-lms-db Removed
 Volume lab-lms-data Removing
 Volume lab-lms-db Removing
 Volume lab-lms-code Removing
 Network lab-lms_default Removing
 Volume lab-lms-data Removed
 Volume lab-lms-db Removed
 Network lab-lms_default Removed
 Volume lab-lms-code Removed

ثم احذف ال Docker Image الذي حملته للمختبر إذا لم تعد تحتاجه، بأمر docker image rm واسمه الكامل، ولا تستخدم أوامر الحذف الجماعي Prune على سيرفر فيه خدمات أخرى.

تدريب عملي

  • غير نوع ترقيم الخيارات في السؤال الأول إلى أرقام، وغير كلمة «مساق» إلى «مقرر» من أداة تخصيص اللغة، ثم افتح الصفحات نفسها وقارن.
  • استعد نسخة المقرر .mbz في مقرر جديد بالأمر admin/cli/restore_backup.php، وتأكد أن الأسئلة الثلاثة انتقلت معه.

الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

  • Moodle 5.3 إصدار LTS يطلب PHP 8.3 وPostgreSQL 17 على الأقل، وتستمر إصلاحاته الأمنية حتى أكتوبر 2029.
  • المختبر ثلاث Containers: الويب، وقاعدة البيانات، وال Cron الذي يعمل كل دقيقة، والمنفذ الوحيد المنشور هو منفذ الويب على 127.0.0.1.
  • منذ الإصدار 5.1 يحتاج Moodle إعداد الموجه Router في سيرفر الويب، و$CFG->routerconfigured في config.php.
  • العربية مترجمة بنسبة 99% وتعمل من اليوم الأول، مع تفاصيل مثل PM وترقيم الخيارات ونصوص غير مكتملة تصلحها بأداة تخصيص اللغة.
  • المقررات والمستخدمون والتسجيل يمكن رفعها من CSV، والدخول الموحد يأتي عبر OAuth 2 مع أي مزود OpenID Connect.
  • النسخة الكاملة هي قاعدة البيانات وmoodledata والكود معاً في وضع الصيانة، ونسخة المقرر لا تغني عنها.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال وتجربته على Moodle 5.3.0 وmoodle-php-apache بإصدار PHP 8.3.35 وPostgreSQL 17.11.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.