في مقال ما هو SCORM كتبنا حزمة بأيدينا، ملف وصف imsmanifest.xml وصفحتي HTML ودالة تبحث عن ال API، وهذا مفيد لتفهم ما يحدث، ولكن لا أحد يبني دورة من عشرين درساً بهذه الطريقة. ففي الواقع يبني المصمم التعليمي الدورة في أداة تأليف Authoring Tool، ويصدرها حزمة SCORM، ثم يأتي سؤالان لا يقلان أهمية: كيف تتأكد أن الحزمة سليمة قبل أن تصل إلى ألف طالب؟ وإذا كانت عندك منصة خاصة بك وليست LMS جاهزاً، فكيف تشغل فيها حزمة SCORM؟
المواضيع في السلسلة:
- ما هو SCORM؟ الإصدارات وكيف يتواصل المحتوى مع LMS.
- أدوات SCORM من التأليف Authoring إلى التشغيل في منصتك (الموضوع الحالي).
- تتبع تقدم المتعلم Learner Tracking من SCORM إلى xAPI.
في هذا المقال سوف نتناول:
- أين تقع كل أداة في الطريق من التأليف إلى الاختبار إلى التشغيل.
- أدوات التأليف التجارية بأسعارها من صفحاتها الرسمية في 22 أكتوبر 2026، والأدوات مفتوحة المصدر، وأين يقف H5P.
- مختبر: دورة صغيرة بنيناها في eXeLearning على Docker، وصدرناها SCORM 1.2، وشغلناها في Moodle 5.3، مع ما وجدناه من إعدادات تمنع وصول الدرجة.
- اختبار الحزمة قبل الرفع: SCORM Cloud وأسعاره، وما يقوله Moodle عن اختبارات المطابقة.
- تشغيل الحزمة نفسها في صفحة ويب عادية بمكتبة scorm-again، وما يرسله المتصفح إلى الخادم الخلفي Backend.
- دعم SCORM في Moodle وOpen edX وCanvas وChamilo، وجدول يساعدك على الاختيار.
أين تقع كل أداة؟
قبل أن ندخل في الأسماء والأسعار، الصورة التالية تبين المراحل الثلاث، وكل أداة في المقال تقع في واحدة منها:

ولاحظ أن مكتبتي pipwerks وscorm-again في المرحلة نفسها ولكن في جهتين مختلفتين، فالأولى داخل الحزمة وتمثل المحتوى، والثانية في صفحة المنصة وتمثل ال LMS، وسوف نراهما معاً في المختبر.
أدوات التأليف Authoring Tools
كل أداة تأليف جادة تصدر SCORM، فالسؤال ليس هل تدعمه، وإنما بأي إصدار، وكم تكلف، وأين تعمل، ومن يستطيع استخدامها في فريقك. والأسعار التالية من الصفحات الرسمية كما قرأناها في 22 أكتوبر 2026، وهي بالدولار الأمريكي قبل الضرائب، وتتغير كثيراً، فراجعها قبل الشراء.
| الأداة | الترخيص والسعر | الصيغ التي تصدرها | أين تعمل |
|---|---|---|---|
| Articulate 360 AI (Storyline وRise) | اشتراك 1,749 دولاراً للمقعد سنوياً للفرق، و1,449 للأفراد بحسب صفحة الأسعار | SCORM 1.2 حتى SCORM 2004 الإصدار الرابع، وxAPI، وcmi5 في Storyline 360 بحسب صفحة الدعم | Storyline برنامج سطح مكتب، وRise في المتصفح |
| Adobe Captivate | 39.99 دولاراً شهرياً للفرد بحسب دليل الشراء، والفرق والتعليم بعرض سعر | SCORM 1.2 وSCORM 2004 وAICC وxAPI بحسب صفحة النشر | برنامج سطح مكتب |
| iSpring Suite | 970 دولاراً للمؤلف سنوياً، وiSpring Suite AI بـ 1,290، وiSpring Cloud AI بـ 720 بحسب صفحة الأسعار | SCORM 1.2 وSCORM 2004 وxAPI وcmi5 بحسب صفحة المنتج | يعمل مع PowerPoint، ومنه نسخة سحابية |
| Elucidat | أصبحت منتجاً اسمه Create داخل منصة أكبر، والسعر بعرض سعر بحسب صفحة الأسعار | لم نجد صفحة رسمية حديثة تحدد الإصدارات | سحابية |
| eXeLearning | مفتوح المصدر برخصة AGPL-3.0، مجاني | SCORM 1.2 وSCORM 2004 وIMS CP وموقع HTML وePub3، وجربناه في المختبر | Docker على سيرفرك، أو برنامج سطح مكتب |
| Adapt | مفتوح المصدر برخصة GPL-3.0، مجاني | SCORM 1.2 افتراضياً وSCORM 2004 الإصدار الرابع بإضافة spoor | إطار عمل Framework، وأداة تأليف على الويب بـ Node.js وMongoDB |
وفيما يلي كل أداة بنقاط قوتها وضعفها من منظور من يدير المنصة، وليس من منظور المصمم وحده:
- Articulate 360 AI: نقاط القوة أن Storyline هو ما يعرفه أغلب المصممين التعليميين، وأنه يغطي كل الصيغ حتى cmi5، وأن Rise يبني دورات تتكيف مع الجوال بسرعة. ونقاط الضعف السعر لكل مقعد، وصفحة الدعم نفسها تقول إن Articulate لم تقدم Storyline لشهادة مطابقة مستقلة.
- Adobe Captivate: نقاط القوة أن له سعراً شهرياً معلناً للفرد، فيناسب مصمماً واحداً في مشروع محدد. ونقاط الضعف أن السعر الشهري يتراكم إذا استمر العمل، وأن صفحات المساعدة من Adobe رفضت أدوات الفحص عندنا يوم الكتابة فلم نقرأ قائمة الصيغ منها مباشرة، فتأكد من خيارات النشر في نسختك.
- iSpring Suite: نقاط القوة أنه يحول عروض PowerPoint الموجودة عند المعلمين إلى دورات، وهذا يختصر الطريق في مؤسسة تعليمية فيها مئات العروض. ونقاط الضعف أنه مبني حول PowerPoint، والسعر لكل مؤلف.
- eXeLearning: نقاط القوة أنه مجاني ويعمل في Container واحد، ويصدر الصيغتين، ويطوره مشروع تقوده وزارة التعليم الإسبانية بحسب مستودعه. ونقاط الضعف أن قائمة لغات الواجهة في ملف README لا تشمل العربية، وأن إرسال الدرجة يحتاج إعدادين لا يظهران للمستخدم الجديد كما سيأتي.
- Adapt: نقاط القوة أن الدورات تتكيف مع الشاشات، وأن إضافة spoor تتيح ضبطاً دقيقاً لما يرسل إلى ال LMS، مثل التفاعلات
cmi.interactionsوحالة الخروج. ونقاط الضعف أن أداة التأليف على الويب آخر إصدار لها 0.11.5 في يناير 2025 بحسب مستودعها، وتحتاج Node.js وMongoDB، وهي أثقل في التشغيل من eXeLearning، لذلك لم نختبرها في هذا المقال.
التجربة العملية: دورة من eXeLearning إلى Moodle
اخترنا eXeLearning لأنه يعمل في Container واحد بقاعدة SQLite داخلية، والإصدار 4.0.5 صدر في 16 سبتمبر 2026 بحسب صفحة الإصدار، والأمر من ملف README في المستودع مع تثبيت الإصدار وحد للذاكرة:
docker run -d --name edu6-exe -p 127.0.0.1:32180:8080 --memory 768m exelearning/exelearning:v4.0.5
docker logs edu6-exe 2>&1 | tail -4Elysia server running at http://localhost:8080
Pages: /login, /workarea
Auth endpoints: /api/auth/login, /api/auth/logout, /api/session/check
Project endpoints: /api/project/*, /api/export/*ملف README يذكر حساباً افتراضياً، ولكن الدخول به في هذه ال Image رجع بالرسالة «Invalid credentials»، فأنشأنا حساباً بأداة سطر الأوامر في ال Container:
docker exec edu6-exe bun run dist/cli.js create-user [email protected] 'change-me-teacher' teacher1SUCCESS User [email protected] created successfully (id: 1)بناء الدورة في المحرر
دخلنا إلى المحرر، وكتبنا عنوان المشروع «أساسيات DNS» في خصائص المشروع Project Properties، ثم أضفنا إلى الصفحة الأولى نشاط «صح أو خطأ» True or false من فئة Assessment and tracking، وكتبنا فيه السؤال «السجل MX يربط اسم النطاق بعنوان IPv4» والإجابة الصحيحة «خطأ»، وترجمنا نصوص الأزرار في تبويب Custom texts إلى «صح» و«خطأ» و«تحقق». وهنا أول ما يجب أن تعرفه: في تبويب SCORM للنشاط ثلاثة خيارات، والافتراضي منها «Do not save the score»، أي أن النشاط لا يرسل أي درجة إلى ال LMS ما لم تغيره:

اخترنا «Automatically save the score» وضغطنا حفظ النشاط، فرفض المحرر الحفظ برسالة «SCORM grades can only be saved in Quiz mode»، أي أن الدرجة لا ترسل إلا إذا فعلت وضع الاختبار من خيارات النشاط في تبويب General settings، ففعلناه وحفظنا. وهذا الإعداد المزدوج هو أكثر سبب سوف تسمعه من المعلمين: «الطالب حل الاختبار والدرجة لا تظهر». والصورة التالية للنشاط بعد الحفظ:

لاحظ ترتيب الكلمات في السؤال، فالمحتوى يعرض من اليسار إلى اليمين، ولاحظ أن الحزمة التي صدرناها كان في صفحتها الرئيسية <html lang="en">، أي أن لغة المشروع بقيت الإنجليزية، فاضبط اللغة في خصائص المشروع قبل التصدير، وافتح الحزمة على منصتك لتتأكد من اتجاه النص.
التصدير بصيغة SCORM 1.2 وSCORM 2004
من قائمة File ثم Download as تجد SCORM 1.2، ولكن في كود الواجهة للإصدار 4.0.5 عنصر SCORM 2004 مخفي بالصنف d-none، بينما أداة سطر الأوامر وملف التصدير فيهما الصيغتان. فصدرنا الحزمتين من واجهة REST التي يطبعها السيرفر عند التشغيل، برقم المشروع من رابط المحرر:
TOKEN=$(curl -s -X POST -H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"change-me-teacher"}' \
http://127.0.0.1:32180/api/auth/login | sed 's/.*"access_token":"\([^"]*\)".*/\1/')
P=24fff377-1f1d-4d05-a5bf-bf3b6b4bb44d
for f in scorm12 scorm2004; do
curl -s -H "Authorization: Bearer $TOKEN" -o dns-$f.zip -w "$f %{http_code} %{size_download}\n" \
http://127.0.0.1:32180/api/v1/export/projects/$P/export/$f
donescorm12 200 312872
scorm2004 200 277860وفي الحزمة الأولى 90 ملفاً، وهذا أول ملف الوصف منها:
<manifest identifier="eXe-MANIFEST-20261022020205W9R1XA"
xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_v1p2">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
<adlcp:location>imslrm.xml</adlcp:location>
</metadata>
<organizations default="eXe-20261022020205W9R1XA">
<organization identifier="eXe-20261022020205W9R1XA" structure="hierarchical">
<title>أساسيات DNS</title>
<item identifier="ITEM-ROOT-20261022020205W9R1XA" isvisible="true">
<title>أساسيات DNS</title>
<item identifier="ITEM-0a4e7af5-ecd0-486c-8707-81b9a7f27158" identifierref="RES-0a4e7af5-ecd0-486c-8707-81b9a7f27158" isvisible="true">
<title>New page</title>
</item>
</item>
</organization>
</organizations>
<resources>
<resource identifier="RES-0a4e7af5-ecd0-486c-8707-81b9a7f27158" type="webcontent" adlcp:scormtype="sco" href="index.html">
<file href="index.html"/>
<dependency identifierref="COMMON_FILES"/>
</resource>
<resource identifier="COMMON_FILES" type="webcontent" adlcp:scormtype="asset">
<file href="content.dtd"/>
<file href="content.xml"/>
<file href="libs/SCOFunctions.js"/>
<file href="libs/SCORM_API_wrapper.js"/>
<!-- 80 more files: theme, bootstrap, jquery, idevices/trueorfalse -->
</resource>
</resources>
</manifest>في الملف أعلاه لاحظ التالي:
- البنية نفسها التي كتبناها بأيدينا في المقال السابق: فهرس فيه عنصر لكل صفحة، ومورد SCO لكل صفحة، ومورد Asset للملفات المشتركة يعتمد عليه الباقي.
- الحزمة تحمل الملف
libs/SCORM_API_wrapper.js، وفي رأسه أنه مكتبة pipwerks SCORM Wrapper برخصة على نمط MIT، أي أن eXeLearning لا يكتب دالة البحث عن ال API بنفسه. - عنوان الصفحة «New page» لأننا لم نغير اسم الصفحة، وهو ما سوف يراه الطالب في قائمة المشغل، فسم الصفحات قبل التصدير.
- حزمة SCORM 2004 فيها
2004 4th Editionوقاعدة تنقلimsss:controlMode choice="true" choiceExit="true" flow="true".
تشغيل الحزمة في Moodle
رفعنا حزمة SCORM 1.2 إلى المقرر نفسه في مختبر Moodle 5.3 من المقال السابق بالسكربت التالي:
<?php
// Adds the SCORM 1.2 package exported from eXeLearning to course NET101.
define('CLI_SCRIPT', true);
require('/var/www/html/config.php');
require_once($CFG->libdir . '/testing/generator/lib.php');
require_once($CFG->dirroot . '/mod/scorm/locallib.php');
\core\session\manager::set_user(get_admin());
$gen = new testing_data_generator();
$course = $DB->get_record('course', ['shortname' => 'NET101'], '*', MUST_EXIST);
copy('/tmp/exe-dns-scorm12.zip', $CFG->dirroot . '/lab-exe.zip');
$scorm = $gen->get_plugin_generator('mod_scorm')->create_instance([
'course' => $course->id, 'name' => 'اختبار من eXeLearning (SCORM 1.2)',
'packagefilepath' => $CFG->dirroot . '/lab-exe.zip', 'maxgrade' => 100, 'grademethod' => GRADEHIGHEST, 'maxattempt' => 0]);
unlink($CFG->dirroot . '/lab-exe.zip');
echo "scorm={$scorm->id} cmid={$scorm->cmid} version=" . $DB->get_field('scorm', 'version', ['id' => $scorm->id]) . "\n";scorm=4 cmid=4 version=SCORM_1.2ثم فتحنا النشاط بحساب الطالب، ولفننا دوال window.API في صفحة المشغل بدالة تسجل كل استدعاء، وذلك بعد أن فتح المشغل الجلسة، لذلك لا يظهر LMSInitialize في السجل. وأجاب الطالب «خطأ» وضغط «تحقق»:
LMSGetValue(cmi.core.lesson_status) -> "not attempted"
LMSSetValue(cmi.core.lesson_status, incomplete) -> "true"
LMSGetValue(cmi.student_data.mastery_score) -> ""
LMSGetValue(cmi.suspend_data) -> ""
LMSGetValue(cmi.core.student_name) -> "خالد الطالب"
LMSGetValue(cmi.core.score.raw) -> ""
LMSSetValue(cmi.core.score.max, 100) -> "true"
LMSSetValue(cmi.core.score.min, 0) -> "true"
LMSGetValue(cmi.core.lesson_status) -> "incomplete"
LMSGetValue(cmi.core.score.raw) -> ""
LMSGetValue(cmi.core.lesson_status) -> "incomplete"
LMSGetValue(cmi.core.lesson_status) -> "incomplete"
LMSSetValue(cmi.suspend_data, exe12/1|idevice-1792634479315-h3uu5uwjf;7;0;1;100;100;0;100) -> "true"
LMSSetValue(cmi.core.score.raw, 100) -> "true"
LMSGetValue(cmi.core.lesson_status) -> "incomplete"
LMSSetValue(cmi.core.lesson_status, passed) -> "true"
LMSSetValue(cmi.core.exit, ) -> "true"
LMSCommit() -> "true"
LMSGetValue(cmi.core.lesson_status) -> "passed"
LMSCommit() -> "true"
LMSCommit() -> "true"في المخرج أعلاه لاحظ التالي:
- الحزمة سألت عن
mastery_scoreفرجعت فارغة، لأن eXeLearning لا يكتبmasteryscoreفي ملف الوصف، فالحزمة نفسها هي التي قررتpassed. - الحزمة تحفظ حالتها الداخلية في
suspend_dataبصيغة خاصة بها تبدأ بـexe12، ولا يفهمها ال LMS، ولا يحتاج أن يفهمها، فهو يحفظها ويعيدها فقط. - الحزمة استدعت
LMSCommitثلاث مرات ولم تستدعLMSFinishطوال الجلسة، أي أنها تعتمد على الحفظ بعد كل خطوة، وهذا ما ينقذها إذا أغلق الطالب الصفحة.

وفي قاعدة البيانات حفظ Moodle الحالة passed والدرجة 100 وبيانات الاستئناف، أي أن الطريق من أداة التأليف إلى سجل الدرجات يعمل، بشرط الإعدادين اللذين ذكرناهما.
اختبار الحزمة قبل الرفع
الطريقة البسيطة أن ترفع الحزمة إلى المنصة الحقيقية وتنتظر شكاوى الطلاب، وهي أسوأ الطرق على الإطلاق، لأن المحاولات الخاطئة تبقى في سجل الدرجات، ولأن بعض الأخطاء لا تظهر لأحد، مثل الإطار الفارغ الذي رأيناه في المقال السابق. والطرق الأفضل ثلاث:
- SCORM Cloud من Rustici: خدمة سحابية ترفع إليها الحزمة وتشغلها وتقرأ سجل الاستدعاءات، وتدعم SCORM وxAPI وcmi5 وAICC بحسب صفحة الأسعار، والاختبار نفسه غير محدود في كل الخطط.
- LMS تجريبي عندك: نسخة من منصتك نفسها بالإصدار نفسه، مثل مختبر Moodle في المقال السابق، لأن الحزمة التي تعمل في SCORM Cloud قد تتصرف بشكل مختلف في منصتك، كما رأينا مع حد
suspend_dataفي Moodle. - اختبارات المطابقة من ADL: سؤال وجواب SCORM في Moodle يذكر أن Moodle يجتاز اختبارات ADL Conformance Test Suite بالإصدار 1.2.7 لـ SCORM 1.2، وهذه الاختبارات تخص من يطور LMS أو أداة تأليف، ولم نتمكن من فتح موقع ADL من شبكتنا يوم الكتابة.
وهذه خطط SCORM Cloud كما في صفحته في 22 أكتوبر 2026، والتسجيل Registration يعني ربط متعلم بمقرر مرة واحدة، ثم يفتحه المتعلم كما يشاء دون تسجيل جديد:
| الخطة | السعر شهرياً | التسجيلات | تكلفة التسجيل الإضافي |
|---|---|---|---|
| Trial | مجانية | 10 قابلة لإعادة الضبط، و3 مقررات حتى 5GB | لا يوجد |
| Tester | 40 دولاراً | 10 قابلة لإعادة الضبط | لا يوجد |
| Little | 90 دولاراً | 50 شهرياً | 3.60 دولار |
| Medium | 180 دولاراً | 100 شهرياً | 3.60 دولار |
| Big | 360 دولاراً | 300 شهرياً | 0.40 دولار |
وللخطط الأكبر حتى 50,000 تسجيل شهرياً أسعار في الصفحة نفسها، ولكن لاحظ أن هذه الخطط لتشغيل المقررات للمتعلمين، وأما اختبار الحزم فالخطة المجانية تكفيه في أغلب الحالات.
تشغيل SCORM في منصتك أنت
لنفرض أن مؤسستك بنت منصة تدريب خاصة بها، وطلب منك أن تشغل فيها حزم SCORM التي اشترتها. والطريقة البسيطة أن تكتب كائن window.API بنفسك بالدوال الثماني، وهذا يعمل مع حزمتك الأولى، ثم تأتي حزمة تكتب قيمة بصيغة غير متوقعة، أو تقرأ عنصراً لم تبرمجه، فتتصرف حزمة تجارية بشكل لا تفهمه. والحل الأنسب أن تأخذ مكتبة تطبق نموذج البيانات كاملاً وتتحقق من القيم، وتكتب أنت التخزين فقط. والصورة التالية تبين ما تكتبه وما تأخذه جاهزاً:

والمكتبة التي جربناها scorm-again، برخصة MIT، وتدعم SCORM 1.2 وSCORM 2004 وتطبق ال API في المتصفح، وأحدث إصدار منها 3.4.7. حملنا الحزمة من npm وأخذنا منها الملف dist/scorm12.min.js إلى مجلد public/lib، وفردنا حزمة eXeLearning في public/course، ثم كتبنا الصفحة التالية:
npm pack [email protected]
tar xzf scorm-again-3.4.7.tgz
mkdir -p public/lib public/course
cp package/dist/scorm12.min.js public/lib/
unzip -q dns-scorm12.zip -d public/course<!doctype html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8">
<title>مشغل SCORM بسيط</title>
<style>
body { font-family: "Segoe UI", Tahoma, sans-serif; margin: 0; display: grid; grid-template-columns: 1fr 520px; height: 100vh; }
iframe { width: 100%; height: 100%; border: 0; border-left: 1px solid #ddd; }
#log { direction: ltr; margin: 0; padding: 12px; background: #17111A; color: #9EE6B8; font: 12px/1.6 Consolas, monospace; overflow: auto; white-space: pre-wrap; }
</style>
<script src="lib/scorm12.min.js"></script>
</head>
<body>
<iframe id="sco" title="المحتوى"></iframe>
<pre id="log"></pre>
<script>
const log = (line) => { document.getElementById('log').textContent += line + '\n'; };
// The runtime the SCO will find in window.parent: scorm-again plays the LMS role in the browser
window.API = new Scorm12API({
lmsCommitUrl: '/commit',
dataCommitFormat: 'json',
autocommit: false,
logLevel: 4
});
// Data the LMS gives the learner before launch (normally from your database)
window.API.loadFromJSON({ core: { student_id: 'student1', student_name: 'Khalid' } });
window.API.on('LMSInitialize', () => log('LMSInitialize()'));
window.API.on('LMSSetValue.cmi.*', (element, value) => log('LMSSetValue(' + element + ', ' + value + ')'));
window.API.on('LMSCommit', () => log('LMSCommit()'));
window.API.on('LMSFinish', () => log('LMSFinish()'));
window.API.on('CommitSuccess', () => log('-> POST /commit OK'));
document.getElementById('sco').src = 'course/index.html';
</script>
</body>
</html>ولكي نرى ما يصل إلى الخادم الخلفي كتبنا سيرفراً صغيراً بـ Node.js دون أي مكتبة، يعرض الملفات ويطبع كل طلب على /commit، وهو مكان قاعدة بياناتك في المنصة الحقيقية:
// A tiny "LMS" for the lab: serves the player page and the SCORM package, and prints each commit it receives.
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { extname, join, normalize } from 'node:path';
const ROOT = '/app/public';
const TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.xml': 'application/xml',
'.png': 'image/png', '.svg': 'image/svg+xml', '.gif': 'image/gif', '.ico': 'image/x-icon', '.json': 'application/json' };
createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/commit') {
let body = '';
for await (const chunk of req) body += chunk;
console.log(new Date().toISOString(), 'COMMIT', body);
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ result: true, errorCode: 0 }));
}
const path = normalize(decodeURIComponent(new URL(req.url, 'http://x').pathname)).replace(/^(\.\.[/\\])+/, '');
try {
const file = path.endsWith('/') ? path + 'index.html' : path;
const data = await readFile(join(ROOT, file));
res.writeHead(200, { 'Content-Type': TYPES[extname(file)] || 'application/octet-stream' });
res.end(data);
} catch {
res.writeHead(404); res.end('not found');
}
}).listen(8080, () => console.log('listening on 8080'));docker run -d --name edu6-scorm-player --memory 128m -p 127.0.0.1:32190:8080 \
-v "$PWD":/app:ro node:24-alpine node /app/server.mjsوفي أول تشغيل حمل المتصفح الصفحة الرئيسية كملف بدل أن يعرضها، والسبب أن السيرفر حدد نوع المحتوى من امتداد المسار / وهو فارغ، فأرسل application/octet-stream، فإذا كتبت سيرفراً لملفات الحزم فحدد النوع من اسم الملف الذي قرأته فعلاً كما في السطر const file أعلاه. ثم فتحنا الصفحة وأجبنا عن السؤال:

وهذا ما طبعه السيرفر عند أول LMSCommit:
2026-10-22T02:11:15.000Z COMMIT {"cmi":{"suspend_data":"exe12/1|idevice-1792634479315-h3uu5uwjf;7;0;1;100;100;0;100","launch_data":"","comments":"","comments_from_lms":"","core":{"student_id":"student1","student_name":"Khalid","lesson_location":"","credit":"","lesson_status":"passed","entry":"","lesson_mode":"normal","exit":"","session_time":"00:00:00","score":{"raw":"100","min":"0","max":"100"}},"objectives":{},"student_data":{"mastery_score":"","max_time_allowed":"","time_limit_action":""},"student_preference":{"audio":"","language":"","speed":"","text":""},"interactions":{}}}في المخرج أعلاه لاحظ التالي:
- scorm-again يرسل كائن
cmiكاملاً في كل حفظ، بخلاف مشغل Moodle الذي رأيناه في المقال السابق يرسل ما تغير فقط، فصمم جدولك ليستبدل الكائن لكل متعلم ومقرر. - اسم المتعلم ورقمه وصلا من
loadFromJSON، وهذا الجزء من مسؤولية منصتك، وكذلك إعادةsuspend_dataوlesson_locationفي الفتح التالي، وإلا بدأ المتعلم من الصفر كل مرة. - الحدث
CommitSuccessلم يطبع شيئاً في سجل الصفحة، والسيرفر استقبل الطلبات الثلاثة وأجاب 200، فلا تعتمد على هذا الحدث في الإصدار الذي جربناه قبل أن تختبره، واعتمد على سجل الخادم.
وهل تتحقق المكتبة من القيم كما يفعل LMS حقيقي؟ جربنا من صفحة المشغل ثلاث قيم مباشرة على window.API:
const r = API.LMSSetValue('cmi.suspend_data', 'x'.repeat(5000)); const e = API.LMSGetLastError();
const r2 = API.LMSSetValue('cmi.core.lesson_status', 'done'); const e2 = API.LMSGetLastError();
const r3 = API.LMSSetValue('cmi.completion_status', 'completed'); const e3 = API.LMSGetLastError();
JSON.stringify([r, e, API.LMSGetErrorString(e), r2, e2, API.LMSGetErrorString(e2), r3, e3, API.LMSGetErrorString(e3)]);["true","0","No Error","false","405","Incorrect Data Type","false","401","Not implemented error"]أي أن المكتبة رفضت حالة غير موجودة في المعيار بالخطأ 405، ورفضت عنصراً من SCORM 2004 في API الإصدار 1.2 بالخطأ 401، ولكنها قبلت 5000 حرف في suspend_data كما يفعل Moodle بإعداده الافتراضي، فإذا أردت أن تكتشف الحزم التي تتجاوز حد SCORM 1.2 فلا تعتمد على المكتبة في ذلك، وافحص طول القيمة في الخادم الخلفي.
window.parent.API، والمتصفح يمنع صفحة من نطاق آخر من الوصول إلى كائنات الصفحة الأم، فإذا وضعت الحزم في خدمة تخزين على نطاق مختلف فلن تجد ال API.دعم SCORM في منصات التعلم
إذا كنت تختار LMS، أو تختار صيغة تطلبها من المزود، فالجدول التالي يجمع ما تحققنا منه من مصادر رسمية:
| المنصة | ما تدعمه | المصدر والملاحظة |
|---|---|---|
| Moodle | SCORM 1.2 مدمج ويجتاز اختبارات المطابقة، وAICC | سؤال وجواب SCORM يقول إن SCORM 2004 غير مدعوم، وحزمة SCORM 2004 البسيطة عملت في مختبرنا |
| Open edX | SCORM 1.2 وSCORM 2004 عبر إضافة | SCORM XBlock برخصة AGPL-3.0، إصدار 19.0.5 في أكتوبر 2026، وتفعله في Advanced Module List للمقرر |
| Chamilo | SCORM 1.2 | توثيق Chamilo 1.11 يقول إنه لا يطبق كل مواصفات SCORM 1.2 لأن كثيراً منها اختياري |
| Canvas | عبر أداة خارجية External Tool | لم نتمكن يوم الكتابة من فتح دليل Instructure الرسمي لدعم SCORM، فاسأل مزود منصتك عن الإصدارات المدعومة قبل الشراء |
ولاحظ ما يقوله ملف README في SCORM XBlock، وهو ينطبق على كل منصة: الحزمة هي التي تقرر متى أكمل المتعلم النشاط، وليس ال LMS، فالحزمة التي لا تكتب حالة الإكمال تترك المتعلم «not attempted» مهما فعل، والإصلاح يكون في أداة التأليف التي أنتجتها.
من يختار ماذا؟
| حالتك | الأنسب | والسبب |
|---|---|---|
| فريق تصميم تعليمي متفرغ يبني دورات تفاعلية كثيرة | Articulate 360 AI | Storyline هو المعيار في هذا العمل ويصدر كل الصيغ حتى cmi5، والسعر يبرره حجم الإنتاج |
| معلمون عندهم عروض PowerPoint جاهزة | iSpring Suite | يحول العروض الموجودة إلى حزم SCORM دون أن يتعلم المعلم أداة جديدة |
| مصمم واحد يحتاج أداة تجارية لمشروع محدد | Adobe Captivate | سعر شهري معلن للفرد بدل اشتراك سنوي لكل مقعد |
| مدرسة أو جهة بلا ميزانية للتراخيص | eXeLearning | مجاني ويعمل على سيرفرك في Container واحد، بشرط أن تتابع إعدادات SCORM لكل نشاط |
| فريق تقني يريد دورات متجاوبة ويتحكم في الكود | Adapt | إطار مفتوح بإضافة تتبع دقيقة، ولكنه يحتاج من يشغل Node.js وMongoDB |
| محتوى تفاعلي قصير داخل Moodle | H5P | لا تحتاج SCORM أصلاً، فالمنصة تقرؤه مباشرة |
| منصة خاصة تريد تشغيل حزم المزودين | scorm-again مع خادمك | المكتبة تطبق ال API، وأنت تكتب التخزين والهوية |
| تشتري حزماً من مزودين كثيرين | SCORM Cloud للاختبار | تكشف أخطاء الحزمة قبل أن تصل إلى منصتك، والخطة المجانية تكفي للاختبار |
مشكلات شائعة وحلولها
| العرض | السبب | الحل |
|---|---|---|
| حزمة eXeLearning تعمل ولا تصل الدرجة | تبويب SCORM في النشاط على «Do not save the score» | اختر الحفظ التلقائي، وفعل وضع الاختبار في General settings |
| المحرر يرفض حفظ النشاط برسالة Quiz mode | اخترت حفظ الدرجة دون وضع الاختبار | فعل وضع الاختبار ثم احفظ |
| لا يوجد SCORM 2004 في قائمة التصدير في eXeLearning 4.0.5 | العنصر مخفي في الواجهة | صدر من أداة سطر الأوامر أو من واجهة REST كما في المختبر |
| النص العربي مختلط الترتيب في الحزمة | لغة المشروع بقيت الإنجليزية lang="en" | اضبط لغة المشروع في الخصائص قبل التصدير، واختبر على منصتك |
| الحزمة في منصتك لا تجد ال API | الحزمة على نطاق مختلف عن صفحة المشغل | قدم الحزم من النطاق نفسه |
| المتصفح يحمل صفحة المشغل بدل أن يعرضها | السيرفر أرسل application/octet-stream | حدد نوع المحتوى من اسم الملف الفعلي |
تنظيف المختبر
docker rm -f edu6-exe edu6-scorm-player
docker rmi exelearning/exelearning:v4.0.5
docker compose down -vتدريب عملي
- أضف إلى خادم المختبر جدولاً بسيطاً، ملف JSON لكل متعلم، يحفظ آخر كائن
cmi، ثم مرره إلىloadFromJSONعند الفتح التالي، وتأكد أنcmi.core.entryأصبحتresumeوأن الحزمة لم تبدأ من الصفر. - صدر الدورة نفسها من eXeLearning بصيغة SCORM 2004، وشغلها بالملف
dist/scorm2004.min.jsوالكائنScorm2004APIفي الصفحة نفسها، وقارن الكائن الذي يصل إلى/commit.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- كل أداة تأليف جادة تصدر SCORM، والفرق في السعر والمنصة ومن يستخدمها: Articulate 360 AI بـ 1,749 دولاراً للمقعد سنوياً للفرق، وiSpring Suite بـ 970، وCaptivate بـ 39.99 شهرياً، وeXeLearning وAdapt مجانيان.
- eXeLearning 4.0.5 يعمل في Container واحد ويصدر الصيغتين، ولكن الدرجة لا تصل إلا إذا اخترت حفظها وفعلت وضع الاختبار في كل نشاط.
- اختبر الحزمة في SCORM Cloud وفي نسخة تجريبية من منصتك قبل أن يراها الطلاب.
- لتشغيل SCORM في منصتك خذ مكتبة مثل scorm-again للمتصفح، واكتب أنت التخزين وإعادة البيانات والهوية، وقدم الحزم من النطاق نفسه.
- Moodle وChamilo يقرآن SCORM 1.2، وOpen edX يقرأ الصيغتين بإضافة، فاطلب من المزود ما تدعمه منصتك فعلاً.
وفي المقال التالي من السلسلة نقارن SCORM بـ xAPI وcmi5 في تتبع تقدم المتعلم من SCORM إلى xAPI، لتعرف متى لا يكفيك SCORM.
سجل التحديثات
- أكتوبر 2026: كتابة المقال وتجربته على eXeLearning 4.0.5 وMoodle 5.3.0 وscorm-again 3.4.7، والأسعار من الصفحات الرسمية في 22 أكتوبر 2026.