fix: play a plain SCORM package, and keep the source where the editor needs it - #96
Conversation
This activity stores the SCORM 1.2 package AS the project: saveToMoodle() exports scorm12 and uploads it, and importPackageFromMoodle() re-opens that same stored package on the next edit. There is no other copy. eXeLearning is about to honour the "Editable export" project property (exportSource) in its SCORM and IMS exporters, which it has always ignored there. An author who turns that property off would then produce a package with no content.xml: exescorm_validate_package() would reject the save as badexelearningpackage, and were the default mandatory-file rule relaxed, the activity would be stranded with nothing left to edit. Ask for the editable source explicitly via the embedding bridge's new forceEditableSource export option, which exists for exactly this case. The flag only governs what the plugin stores; an export the author triggers from inside the editor still honours their choice. Refs exelearning/exelearning#2415
The mandatory-files rule defaulted to requiring a root content.xml, which rejected every plain SCORM package on upload -- and, once eXeLearning honours its own "Editable export" property, eXeLearning's own output too (exelearning/exelearning#2415). Nothing in this plugin reads content.xml at runtime, and exescorm_validate_package() already enforces SCORM validity on its own by requiring a root imsmanifest.xml or an AICC .cst, so the rule was gatekeeping rather than validating. Default it to empty: a SCORM package uploaded here now plays, whoever made it and whether or not it kept its source. The setting stays, so a site that wants eXeLearning-authored packages only re-adds the expression; the upgrade step clears the stored value only when it is still the old default byte for byte, leaving a customised list alone. Require the source where it genuinely matters instead. set_ode.php is the embedded editor saving, and the editor re-opens that exact package on the next edit, so refuse a source-less save there rather than strand the activity. For the same reason editor/index.php now refuses to open a package with no source: the view page never offers the "Edit in eXeLearning" button for an uploaded package, but the URL was reachable and the editor would have failed at import time with no explanation. Also fix tests/validatepackage_test.php, which asked for validexescorm.zip and badexescorm.zip -- fixtures that have never existed in the repository, so the test errored before asserting anything. The files are validscorm.zip and badscorm.zip. Refs exelearning/exelearning#2415
There was a problem hiding this comment.
Thanks for this, @erseco.
The diagnosis on part 1 is right, and the fixture fix in validatepackage_test.php was overdue. Two things before merging:
Blocking: the save-time check is in the wrong endpoint
The PR says "set_ode.php is the embedded editor saving, and editor/bridge.js re-opens that exact package on the next edit". I think that is not the case:
set_ode.phpis the callback for the external eXeOnline flow (exeonlinebaseuri, seeclasses/exeonline/exescorm_redirector.php:122).- The embedded editor saves through
editor/save.php(editor/index.php:88,lib.php:316). That file calls neitherexescorm_validate_package()norhas_editable_source(): it writes the ZIP straight into thepackagefilearea, updatesreferenceand callsexescorm_parse().
So once the bundled editor picks up exelearning/exelearning#2417, if forceEditableSource is ever not honoured, editor/save.php will happily store a source-less package and the activity ends up in exactly the unrecoverable state this PR wants to prevent. The safety net covers the old flow, not the one bridge.js uses.
Suggested fix: add the same check to editor/save.php — create the stored_file, list_files() + has_editable_source(), and fail (success: false, nothing written to reference, no exescorm_parse()) when the source is missing. Please also fix the comment in set_ode.php and the PR description. Keeping the check in set_ode.php is fine: that flow was already rejected by mandatoryfileslist before, only the message changes.
Minor (non-blocking)
editor/save.php does not call exescorm_validate_package() at all. Pre-existing, but since you will be touching that file anyway, it is the natural place to run both checks in the same pass.
Could you please take a look at those points?
Thank you.
The embedded editor saves through editor/save.php, not set_ode.php: the latter is the callback of the external eXeOnline flow. editor/save.php wrote the upload straight into the package filearea with no validation, and mod_form.php skips validation for the embedded type, so a package without an eXeLearning source could be stored and leave the activity with nothing to edit. Stage the upload in temppackage, as set_ode.php does, run exescorm_validate_package() and has_editable_source() on it, and on failure delete the staged file and answer success: false with the reason. Nothing is written to package, reference is untouched and exescorm_parse() is not called, so the activity keeps its previous package. Correct the comments in set_ode.php and bridge.js that described the wrong flow, and add a test that a real archive with imsmanifest.xml and content.xml passes both checks.
|
Addressed in c98a2a1. Save-time check. Confirmed: the embedded editor saves through
The Minor. Covered by the same pass: Tests. Verification (Moodle 4.5.10 / PHP 8.3.15 / MariaDB 12.3.3):
PR description updated to match. One thing I noticed and left alone: |
Addressed in c98a2a1 (editor/save.php now runs both checks); re-requesting review.
ignaciogros
left a comment
There was a problem hiding this comment.
Thanks, @erseco.
I translated the source-check strings and added the change in the CHANGELOG.
I hope those changes are OK too.
I agree about opening a separate issue for the amd/src/editor_modal.js problem.
Refs exelearning/exelearning#2415 · pairs with exelearning/exelearning#2417
Two separate things, because
content.xmlwas being asked to do two jobs at once.1. A SCORM package without
content.xmlshould just playexescorm/mandatoryfileslistdefaulted to/^content(v\d+)?\.xml$/, so any package without an eXeLearning source was rejected on upload withbadexelearningpackage— a stock Articulate/iSpring SCORM, and now eXeLearning's own output once it honours the Editable export property.That rule was gatekeeping, not validating:
content.xmlat runtime — there is not one reference to it in PHP outside this check;exescorm_validate_package()already enforces SCORM validity independently, requiring a rootimsmanifest.xmlor an AICC.cst(lib.php:1382), with its ownnomanifest/badimsmanifestlocationmessages;view.php:181offers "Edit in eXeLearning" only forEXESCORM_TYPE_EMBEDDEDor an.elpx.So the default is now empty. Such a package uploads, plays, tracks, and simply has no edit button. The setting stays for sites that want eXeLearning-authored packages only — they re-add the expression.
db/upgrade.phpclears the stored value only when it is still the old default byte for byte, so a customised list is left alone.forbiddenfileslist(/.*\.php$/) is untouched — that one is security, not policy.2. Require the source where it actually matters
Two endpoints store a package that will be re-opened for editing, and both now refuse one without an eXeLearning source:
editor/save.phpis where the embedded editor saves (editor/index.phphands it tobridge.jsassaveUrl), andbridge.jsre-opens that exact package on the next edit. It used to write the upload straight into thepackagefilearea with no validation at all, andmod_form.phpdeliberately skips validation for the embedded type, so this was the only gate on that path and it was open. It now stages the upload intemppackage(the same sequenceset_ode.phpuses), runsexescorm_validate_package()andhas_editable_source()on it, and on failure deletes the staged file and answers HTTP 400{success: false, error: <reason>}. Nothing is written topackage,referenceis untouched andexescorm_parse()is not called, so the activity keeps the package it had.set_ode.phpis the callback of the external eXeOnline flow (exescorm_redirectorsends the author toexeonlinebaseuri, andget_ode.phphands the stored package back to eXeLearning on the next edit). The same check applies there; that flow was already rejected by the oldmandatoryfileslistdefault, only the message changes.A source-less package in either place is unrecoverable, so it is refused explicitly instead of inherited from a global RE list that also governs unrelated uploads.
editor/index.phpgets the same check. The view page never offers the edit button for an uploaded package, but the URL was reachable with only a capability check, and the editor would have loaded and then failed at import with no explanation. It now shows a reason.And the export request sets
options: {forceEditableSource: true}(exelearning/exelearning#2417), so an author who turns the property off can still use the embedded editor: the package it stores keeps its source, while the property still governs every export the author triggers themself.Drive-by
tests/validatepackage_test.phpasked forvalidexescorm.zipandbadexescorm.zip. Neither has ever existed in this repository — the fixtures arevalidscorm.zipandbadscorm.zip— sotest_validate_packageerrored increate_file_from_pathnamebefore asserting anything. Fixed, since the new tests live in that file.Verification
Moodle 4.5.10 / PHP 8.3.15 / MariaDB 12.3.3,
docker compose up+vendor/bin/phpunit.mod/exescorm/tests/validatepackage_test.php— OK (5 tests, 21 assertions). On the unmodified tree the same file isTests: 1, Errors: 1(the missing fixture).mod_exescorm_testsuite— before:Tests: 203, Errors: 45, Failures: 8; after:Tests: 207, Errors: 44, Failures: 8. The 4 extra tests are the new ones and the error that disappeared is the fixture name. No new failures; the remaining ones are pre-existing PHP 8.3 dynamic-property deprecations.phpcs --standard=moodle-extraon every changed file: error counts identical to the unmodified tree, exceptsettings.phpwhich drops one (a tab-indented line removed).editor/index.php,editor/save.phpandlang/en/exescorm.phpare clean.New tests: a plain SCORM package validates; a site that re-adds the mandatory rule still rejects it;
has_editable_source()across root/nested/legacycontentvN.xml/near-miss names; and a real archive withimsmanifest.xml+content.xmlpasses both save-time checks.Ordering
Safe to merge in either order. The bundled editor under
dist/is built from a pinned eXeLearning checkout (make build-editor); until that checkout includes exelearning/exelearning#2417,forceEditableSourceis an unknown key the editor ignores and the current editor still always writescontent.xml. Part 1 of this PR stands on its own regardless.Longer term
The cleaner shape is still to keep the
.elpxsource in its own file area beside the published SCORM package, so the author'sexportSourcechoice would apply to what learners receive while the source stays private to the plugin. That needs a new file area plus backup, restore and upgrade paths — follow-up, not a precondition. Recorded inADR-2415-01on the core PR.Moodle Playground Preview
The changes in this pull request can be previewed and tested using a Moodle Playground instance.
ℹ️ The eXeLearning editor is fetched from the shared release and unpacked into the plugin when the playground boots, so the first load may take a few extra seconds. ELPX upload, viewer and preview work normally.