في مقال أنظمة إدارة التعلم 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 على السيرفر مباشرة كما في دليل التثبيت الرسمي. والصورة التالية تبين المختبر الذي سوف نبنيه:

ملفات المختبر
أنشئ مجلداً للمختبر، وضع فيه الملف 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/testTest 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-cronCron run completed correctly
Cron completed at 22:03:39 in 0.581148 seconds. Memory used: 30.1 ميغا بايت.
Cron run completed correctly
Cron completed at 22:04:40 in 0.595744 seconds. Memory used: 29.4 ميغا بايت.الفاصل بين التشغيلين دقيقة كما أردنا، وفحص النظام أظهر الحالة OK للفحص tool_task_cronrunning. ولاحظ النص قبل كلمة ميغا بايت، وهو مسافة HTML مكتوبة داخل الترجمة العربية فتظهر حرفياً في سطر الأوامر، وهو أول مثال من الأمثلة التي في القسم التالي.
العربية واتجاه الكتابة RTL بصراحة
حزمة اللغة العربية لإصدار 5.3 مترجمة بنسبة 99% بحسب صفحة حزم اللغة، أي 32610 نصاً من 32811، وفي ملف إعدادها thisdirection بقيمة rtl، فتنقلب الواجهة كلها إلى اليمين، والقوائم والجداول والأزرار تعمل كما ينبغي في كل الصفحات التي فتحناها. والصورة التالية لصفحة أسئلة الاختبار الذي أنشأناه:

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

والخلاصة أن العربية في 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,studentdocker 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يعني أن كلمة المرور في الملف، وفي الإنتاج مع الدخول الموحد لن تحتاج كلمات مرور في الملف أصلاً.
والصورة التالية لصفحة المشاركين في المقرر كما تراها المعلمة، ولاحظ فيها فلتر الأسماء بالحروف العربية تحت فلتر الحروف الإنجليزية:

اختبار Quiz حقيقي يحله طالب
أنشأنا الاختبار من الواجهة كما يفعل المعلم، بثلاث خطوات:
- إضافة النشاط: من صفحة المقرر في وضع التحرير، أضف نشاطاً من نوع اختبار Quiz في القسم الأول، واكتب عنوانه، ونحن سميناه «اختبار الوحدة الأولى: أساسيات الشبكات».
- إضافة الأسئلة: من صفحة الأسئلة اختر «أضف» ثم «سؤال جديد»، وأضفنا سؤالين من نوع الاختيار من متعدد Multiple Choice عن DNS وDHCP، وسؤالاً من نوع صح وخطأ True/False عن منفذ HTTPS، وكل سؤال بدرجة واحدة، والدرجة القصوى للاختبار 10.
- المحاولة: دخلنا بحساب الطالب خالد، وبدأنا المحاولة، وأجبنا الأول صحيحاً والثاني خطأ، وتركنا الثالث دون إجابة، ثم سلمنا.
والنتيجة كانت كما في الصورة السابقة: 1.00 من 3.00، أي 3.33 من 10.00 بنسبة 33.33%، والفترة 12 ثانية. والصورة التالية لتقرير المقيم Grader Report في سجل الدرجات كما تراه المعلمة:

لاحظ أن المتوسط العام 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. والخطوات باختصار:
- في authentik، أنشئ مزوداً من نوع OAuth2/OpenID وتطبيقاً له، وعنوان الرجوع
https://lms.example.com/admin/oauth2callback.php، واحفظ Client ID وClient Secret. - في Moodle، من إدارة الموقع ثم الخادم ثم خدمات OAuth 2، أنشئ خدمة مخصصة Custom، واكتب عنوان المزود في حقل Issuer URL مع المعرف والسر.
- فعل إضافة المصادقة OAuth 2 من إدارة الموقع ثم الإضافات ثم المصادقة، فيظهر زر الدخول بحساب المؤسسة في صفحة الدخول.
- اربط حقول المستخدم 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
الطريقة البسيطة في الترقية هي أن تنسخ الملفات الجديدة فوق القديمة، وهي أسوأ الطرق على الإطلاق، لأن الملفات التي حذفها الإصدار الجديد تبقى في مكانها، وقد تكسر الموقع أو تبقى ثغرة فيه. والطريقة الصحيحة كما يلي:
- تأكد أن الإصدار الذي أنت عليه يسمح بالانتقال، فالإصدار 5.3 يطلب 4.4 أو أحدث، ومن كان على إصدار أقدم يرقي على مراحل.
- تأكد أن كل إضافة Plugin مثبتة لها إصدار متوافق مع الإصدار الجديد، فالإضافة التي لا تدعمه توقف الترقية.
- خذ نسخة كاملة كما في القسم السابق، وجرب الترقية على نسخة من الموقع أولاً.
- ضع الموقع في وضع الصيانة، وضع الكود الجديد في مجلد جديد أو Volume جديد، ثم انقل إليه
config.phpوالإضافات التي ليست من الكود الأساسي. - شغل
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 بالإعدادات الافتراضية للذاكرة المؤقتة. والصورة التالية تبين المراحل الثلاث:

في المخطط أعلاه لاحظ التالي:
- عندما يصبح للموقع أكثر من سيرفر ويب، يجب أن يكون
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=securityCRITICAL: تحقق من كل المسارات العمومية/الخاصة (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.