Video Assessment Module
Maintained by
Brendon M, Matthew Cotter
Video Assessment Module (VAM) enables rubric-based formative assessment of recordings of learners’ live skill performances. Students can upload videos of their skill demonstrations and assess themselves and/or receive feedback from peers and teachers.
Price option: Free
Supports Moodle 4.0-5.2
See all versions
Latest release: 1 month ago
Installations: 39
Downloads (last 90 days): 94
Frankenstyle name:
mod_videoassessment
Plugin type:
Activities
Versions
Current version
1.1.8 (Build: 2026062815) (2026062815)
Moodle 4.5, 5.0, 5.1, 5.2
Uploaded on Monday, 6 July 2026 at 04:17 (GMT+0)
1.1.8 (Build: 2026062815)
Plugin type: Activities
Frankenstyle component name: mod_videoassessment
Version build number: 2026062815
Version release name: 1.1.8 (Build: 2026062815)
Maturity: Stable
Supported Moodle versions: 4.5, 5.0, 5.1, 5.2
Repository URL (Git): https://github.com/BM373/moodle-mod_videoassessment.git
Repository branch:
Repository tag:
Issue/bug tracker URL: https://github.com/BM373/moodle-mod_videoassessment/issues
Changelog
All notable changes to mod_videoassessment will be documented in this file.
This project adheres to Keep a Changelog,
and (from this fork onwards) uses Semantic Versioning.
Unreleased
1.1.8 - 2026-06-28
Added
- GitHub Actions workflow
moodle-ci.ymlrunning the Moodle Plugin CI matrix across
Moodle 4.5 / 5.0 / 5.1 / 5.2 - MariaDB / PostgreSQL in parallel. CHANGELOG.md(this file) introduced for the Shinonome Labo fork..gitattributesenforcing LF line endings on all first-party text files.
Changed
version.php: declare support for Moodle 4.5 LTS through 5.2 ($plugin->supported = [405, 502]),
raise the minimum required Moodle version to 4.5 LTS ($plugin->requires = 2024100700),
and set the release to1.1.8 (Build: 2026062815).README.mdrefreshed for the 1.1.x release line: corrected the supported Moodle
range (4.5 LTS – 5.2), added a current-version banner, noted PostgreSQL support,
and replaced the inline change log with a pointer toCHANGELOG.md.- Convert all first-party source files (PHP, JS sources, CSS, Mustache, YAML, Markdown,
etc.) from CRLF to LF line endings.
Security
- SSRF hardening of the external-video thumbnail fetch.
video_embed::thumbnail_url()
builds a PeerTube/Esup-Pod oEmbed endpoint from the (user-supplied) video host and
requests it server-side. It now only fetches when the host is on the admin
trusted-embed allowlist and forces https, andoembed_thumbnail()additionally
rejects any host that resolves to a private or reserved address (loopback, RFC1918,
link-local, ...), so a student-supplied link cannot be used to probe internal
services. - Pre-release security review (2026-06). Hardened content that is authored by one
user and rendered in another user's session:
- Stored XSS — comments modal. The "show all comments" web service
(mod_videoassessment_get_getallcomments,externallib.php) now purifies
grader feedback withformat_text()instead of rendering it with
noclean => true. Moodle's HTML Purifier keeps the<video>/<audio>/
<source>tags recorded feedback needs while stripping injected script, so a
peer/self grader can no longer land script in the assessed user's or teacher's
session. - Stored XSS — video filename. The uploaded
originalnameis now escaped
withs()on the teacher "Videos" management table (classes/va.php) — both
the cell value and the (now quoted) YouTube linkhref. - CSRF — bulk upload.
bulkupload/ajax.phpnow callsrequire_sesskey()
before the state-changing upload / progress actions (the AMD uploader already
sendsM.cfg.sesskeywith every request). - iframe sandbox. Dropped
allow-popups-to-escape-sandboxfrom the external
embed sandbox (classes/renderer/renderer.php) so a popup opened from a
trusted embed can no longer escape into an un-sandboxed top-level window;
allow-forms,allow-top-navigationandallow-modalsremain withheld and
clipboard-writeis not granted.
- Stored XSS — comments modal. The "show all comments" web service
- External-embed trusted-host allowlist (from the 2026-06-19 review) re-verified
intact and fail-closed: host-agnostic providers (PeerTube, Esup-Pod, Opencast,
generic embed) are gated byvideoassessment/trustedembedhosts
(classes/video_embed.php::host_is_trusted()/default_trusted_hosts()); an
unlisted host degrades to a plain link instead of an iframe.
Fixed (post-release testing)
- Mobile assess video aspect ratio (root fix). An external embed is an
<iframe>
with no intrinsic aspect ratio, so sizing it withwidth:100%+height:auto(and
assess.jsstripping its width/height attributes at runtime) stretched it into a
horizontal banner ("728x90"). Sizing is now owned entirely by the.video-wrapbox,
which sets a fixed aspect ratio (16:9, or 9:16 for YouTube Shorts via:has(.shorts))
and has the player fill it absolutely — so YouTube, Vimeo, Opencast Paella and
PeerTube all render correctly proportioned. The competingassess.jsiframe rewrite
was removed. - Mobile assess video no longer disappears while its audio keeps playing. Focusing a
rubric comment box called anassess.jshandler that set the video container to
display:none; an external iframe keeps its sound underdisplay:none, so the video
vanished but the audio continued. That handler is now a no-op — the sticky video band
above the rubric already stays clear of the on-screen keyboard. - Plugin CSS cache-busting:
view.css,assess.cssandgetHTMLMediaElement.css
are linked directly (outside Moodle's revved theme pipeline), so a phone kept the
old stylesheet long after an upgrade — testers repeatedly saw the previous build's
mobile layout (e.g. an off-screen-parked video band) until they hard-refreshed.
The links now carry a?v={plugin version}query, so every release invalidates
the browser cache automatically. - Mobile grading: on the 採点 (Grading) tab the recording now stays visible as a
compact, height-capped band above the rubric (2026-07 customer feedback: "the
video used to be visible above the rubric" — graders watch while they score). The
band sticks to the top of the viewport (just under the tab bar) so it stays
referenceable while the grader scrolls the rubric down (further 2026-07 feedback).
The 34vh cap keeps a tall portrait recording from burying the criteria, the 動画 tab
still gives the video the full viewport, and playback continues across tab switches
because the band is never display:none'd, parked or re-parented. Contract tests
updated to pin the new visible-band behaviour. - Mobile grading: the assess screen's mobile-portrait tab bar (動画 / 採点) defaulted
to the Video tab, which parks the rubric off-screen — graders opened the page, saw
only the video and read it as "the rubric doesn't show / can't grade on a phone".
The first visit now lands on the Grading tab so the rubric is immediately visible;
the video is one tap away and playback still continues across tab switches, and
the user's last-picked tab is still remembered for reloads. - Mobile grading: on a phone the rubric table is far wider than the viewport, so its
right-hand levels and the score-input column overflowed off-screen and a teacher
could not reach the cells to grade.assess.cssnow keeps the rubric within the
viewport and lets it scroll horizontally (touch-friendly) below the 768px breakpoint. - Opencast: a Tobira
/v/{id}share link is now embedded via Tobira's iframe-safe
player route/~embed/!v/{id}(the exact URL Tobira's own Share -> Embed dialog
hands out; note the!). The/v/{id}page frame-busts inside an iframe ("This
page can't be embedded") and/~embed/v/{id}without the!returns "Page not
found", soresolve_opencast()rewrites to the correct form. The/v/{id}link is
also matched when nested under a Tobira realm path (e.g.
/conferences/2021/summit/v/{id}) -- the id alone identifies the video. It also
recognises/play/{id}, the Paella and Theodul players. - Feedback-video indicator: when grader feedback contains a recorded
<video>, the
report's General Comments now shows a localised "[See video]" play-icon primary
(blue) button on every device (it previously only appeared on a phone, and the
video was shown inline on desktop), so a student can always tell at a glance that a
feedback video is waiting and tap to play it in a modal. A text-only comment keeps
the "[See comment]" speech-bubble button (a phone teaser; full text inline on
desktop), instead of the old opaque "...". The modal now renders the recording as a
native<video>(get_getallcommentsformats withfilter => false, scoped by
.va-feedback-modalCSS): the AJAX-injected content never gets a Video.js instance,
so the old filtered "video-js" player collapsed to a broken sliver on desktop. No
leaked@@PLUGINFILE@@URL teaser is shown for a video anymore. - Untrusted external-embed hosts now show a clear notice instead of silently
degrading to a bare link. When a host-agnostic provider's host is not on the
trustedembedhostsallowlist,video_embed::blocked_host()distinguishes that
case from an unrecognised URL and the renderer shows a warning ("host not on the
trusted list"), a button to open the video in a new tab, and the admin path to add
the host — rather than a plain link that looked like the player had failed. - External-video thumbnails no longer render as a broken image. Externally-linked
videos other than YouTube (Vimeo, PeerTube, Esup-Pod, Dailymotion, Opencast,
generic embeds) had no thumbnail URL, so the score/overview and Videos tables
emitted<img src="">. A sharedva::external_video_thumb()helper now renders a
quoted<img>only when a thumbnail exists and a neutral "External video"
placeholder otherwise, and the misleading hard-coded "Video in Youtube" label is
replaced by the localisedexternalvideostring. Vimeo links additionally fetch a
real still via the public oEmbed endpoint (vimeo_url::thumbnail_url(), https +
Vimeo-CDN-host validated, fail-safe to the placeholder on any network/parse error). - Real thumbnails extended to the other providers via
video_embed::thumbnail_url(): PeerTube and Esup-Pod are read through their oEmbed
endpoints (/services/oembedand/video/oembed/; Esup-Pod's host-relative
thumbnail is resolved against the video host), and Dailymotion uses its derivable
static still. Thumbnails are vetted (https, served by the video host or a sub-domain)
and fail-safe to the placeholder. Opencast/Tobira expose no thumbnail, so it keeps
the placeholder.
Fixed (customer-requested 2026-04 fixes)
- #7 Smartphone UX hardening for the assess screen. SGU's
recordings showed two concrete iOS problems: the floating video
container slid under the Home indicator, and tapping a per-criterion
.remark textarea(or the final feedback editor) pushed the focused
field behind the on-screen keyboard. Two new rules inassess.css:
(a) the floating / sticky video container now reserves the iOS
safe-area insets viapadding: env(safe-area-inset-*, 0), and
(b) every focusable feedback field has ascroll-margin-top/
scroll-margin-bottomof35vh, so the browser's implicit
scrollIntoView()on focus keeps the field above the keyboard.
The contract is pinned bytests/mobile_ui_test.php(three
data-driven assertions on the CSS file content). - #8 Honour
$CFG->preventexecpathon the FFmpeg / MP4Box admin
settings, in the spirit of upstream PR #58 by Adam Jenkins. Both
admin_setting_configtext_ffmpegcommand::validate()and
admin_setting_configtext_mp4boxcommand::validate()now refuse any
change from the Web UI when the global flag is set, returning a
localised "this executable path is locked" message
(admin_settings_executable_locked, en + ja). The renaming of
setting keys to thevideoassessment/Xstyle proposed in the same
upstream PR is intentionally deferred (it would require a
db/upgrade.phpmigration of every site's existing config). The
install-time FFmpeg auto-detection from upstream PR #57 (Hipjea /
fondation-unit) is also tracked for a follow-up: the security
surface it touches is already hardened by the
mod_videoassessment\admin\command_validatorintroduced in #9.
Smoke test intests/admin/preventexecpath_test.php. - #12 Add a "Finish making rubric ' Go to assess" navigation
button on the rubric edit screen. The page is owned by Moodle core
(/grade/grading/form/rubric/edit.php), so the button is injected
via a Moodle 4.5+ hook callback registered indb/hooks.php. The
callback\mod_videoassessment\hook_callbacks::inject_finish_rubric_button
fires on every page render, calls
rubric_navigation::is_videoassessment_rubric_edit_url()to scope
the injection, and queues the AMD module
mod_videoassessment/finish_rubric_buttononly when the page is the
rubric edit form for a videoassessment activity. URL classification
- assess-page URL building live in
\mod_videoassessment\rubric_navigation,
exercised by 7 data-driven tests intests/rubric_navigation_test.php.
New language stringfinishmakingrubric(en + ja).
- assess-page URL building live in
- #6 Videos recorded inside the teacher's "Feedback Box" editor are
preserved through the display pipeline. Bothview.php's
getallcommentsAJAX branch andclasses/print_page.php's comment
rendering now passnoclean => truetoformat_text()so the HTML5
<video>/<source>markup produced by Moodle's recordrtc
Atto/Tiny plugin survives the purifier pass. Without this flag the
cleaner stripped the media tags, leaving teacher feedback visible
but unplayable. The contract is pinned by
tests/feedback_video_display_test.php, which feeds an
@@PLUGINFILE@@placeholder through the rewrite + format pipeline
and asserts that the resulting HTML still contains both<video>
and<source>tags pointing at/mod_videoassessment/submissioncomment/. - #13 Live "current grade in gradebook" display on the assess
screen. The\mod_videoassessment\rubric_totalcalculator computes
total / max / percentagefrom a snapshot of selected rubric levels
(covered bytests/rubric_total_test.php); the new
mod_videoassessment/live_grade_totalAMD module mirrors the same
math client-side and refreshes a[data-vassmt-live-grade]
indicator next to the saved score every time a rubric cell is
clicked.va::view_assess()requires the new module, and
classes/form/assess.phpinjects the indicator span into the
Current grade in gradebook:cell. - #3 Cap in-browser recording at 2 minutes and surface the limit on
the radio label. The new\mod_videoassessment\recordinghelper is
the single source of truth for the duration cap (120 seconds, 2
minutes);amd/src/record.jsreads the same constant via a hard-
coded mirror, starts asetTimeoutwhen recording begins, and calls
finishRecording()automatically when the cap is reached. The
Englishrecordnewvideolabel gains a(max. length 2 minutes)
suffix; the Japanese label is updated to「--い動画'録画("大録画時-: 2分)」.
tests/recording_test.phppins both the cap value and the label
contract. - #2 Replace the single site-level
videoassessment_preventvideouploads
toggle with three independent allow-flags that mirror the
per-activity "Video submissions" group:
allowexternallinks(default ON) - controls the YouTube / Vimeo /
esup-portail / generic external link channel.allowvideouploads(default ON) - controls direct file uploads.allowvideorecording(default ON) - controls in-browser recording.
mod_form.phpreads each flag and locks the matching activity
checkbox when the corresponding site flag is OFF.db/upgrade.php
derives the new flags from the legacypreventvideouploadsvalue at
the new savepoint2026050200, preserving each existing site's
effective behaviour without administrator intervention. New language
strings (fileuploadlinks/allowexternallinks/allowvideouploads
/allowvideorecordingand their_helpvariants) are added in en
and ja in the correct alphabetical position.
- #4 YouTube Shorts compatibility. The new
\mod_videoassessment\youtube_urlhelper extracts the canonical
11-character video id from any common YouTube URL form (standard
?v=,youtu.be/,/shorts/,/embed/, mobilem., no-cookie),
reportsis_shorts(), and yields the canonical thumbnail and
embed URLs (with optional GDPR-friendlyyoutube-nocookie.com
host).va::view_upload_video()now routes through
youtube_url::extract_id()instead of the legacy
explode('=', $url), so portrait-mode Shorts URLs no longer break
when a learner submits one. The change is covered by data-driven
tests intests/youtube_url_test.php(12 URL forms exercised). - #1 Generalise the wording of the upload / link UI so it no longer
implies YouTube exclusivity. The English stringsallowyoutube(now
"Allow external video links (e.g. YouTube)"),uploadingvideo
("Upload / link video"),uploadvideo("Upload / link a video"),
reuploadvideoanduploadyoutube("Insert External Video Link")
are updated, and therecordnewvideo_helpparagraph now reflects
YouTube Shorts support and explicitly mentions Vimeo and
esup-portail/Pod as additional accepted hosts. The Japanese (ja)
translations are kept in sync, with newallowyoutube/
allowyoutube_helpentries inserted in the correct alphabetical
position so the LangFilesOrdering sniff stays clean. - #5 Replace the random peer-assignment algorithm in
va::get_random_peers_for_users()with a load-balancing pass that
tracks how often each user has already been chosen and always picks
the candidate with the lowest count (random tiebreak). The previous
algorithm only ensured that each user receivednumpeerspeers,
which meant some users were chosen as a peer many more times than
others. The new contract -- every user is chosen within ±1 of the
expected mean -- is pinned bytests/peer_assignment_test.php.
Non-student