MDL Shield

Poster

mod_poster

Print Report
Plugin Information

Poster activity module (mod_poster). Lets teachers build a page out of blocks arranged in two custom block regions. The blocks show either on the activity's own view page or inline on the course page.

Version:2026102800
Release:9.0.0
Reviewed for:5.3
Privacy API
Unit Tests
Behat Tests
Reviewed:2026-10-28
27 files·2,011 lines
Grade Justification

The review found no security vulnerabilities. Every entry point is protected with the correct core controls:

  • view.php calls require_course_login() with the course module and require_capability('mod/poster:view').
  • The editing-mode toggle checks confirm_sesskey() and user_allowed_editing().
  • index.php calls require_course_login().
  • Inline rendering on the course page checks uservisible and the view capability. The core block manager enforces moodle/block:view for each block.
  • All database access goes through parameterised $DB calls. Output is escaped through format_string() and format_module_intro(), or Mustache auto-escaping.
  • DDL appears only in db/upgrade.php.

The two findings are low-severity defects in how inline posters render on the course page. Duplicate HTML element IDs appear when more than one inline poster is shown. JavaScript requirements registered by blocks on a throwaway page object are discarded. Neither has a security impact. The plugin also has a correct null privacy provider, complete backup and restore, events, and good PHPUnit and Behat coverage.

AI Summary

mod_poster is a small activity module of about 1,900 lines including tests. It gives teachers a page made of blocks in two custom regions (mod_poster-pre and mod_poster-post). The poster shows either on its own view page or inline on the course page. The plugin stores no user data: only one instance table, with name, intro and display settings.

Its security is sound. The view page enforces course login and the mod/poster:view capability. The editing toggle checks the sesskey and the editing capabilities. Inline rendering checks uservisible and the view capability before showing blocks. All output goes through format_string(), format_module_intro() or Mustache escaping. Database access is parameterised throughout. Backup and restore, events, completion and the null privacy provider are all implemented correctly.

Both findings are low-severity defects in inline display on the course page:

  • The content template uses fixed element IDs, which become duplicates when a course shows more than one inline poster.
  • Blocks are rendered against a separate moodle_page, so any JavaScript they register is never sent to the browser.

Neither affects security.

Findings

code qualityLow
Fixed element IDs duplicate when several inline posters share a course page

The content template hard-codes id="mod_poster-content" and id="mod_poster-content-region-{{suffix}}". When a poster's display is set to the course page, poster_cm_info_view() renders this template for every inline poster. A course with two or more inline posters therefore ends up with duplicate id attributes in one document. That is invalid HTML, can confuse assistive technology, and makes CSS or JS selectors and Behat steps that target these IDs match only the first poster.

Risk Assessment

Low risk. This is a markup and accessibility defect with no security impact. It only appears when a course shows several inline posters.

Context

mod_poster_renderer::view_page_content() is used both on view.php (one poster per page) and from poster_cm_info_view() for each inline poster on the course page. The template was written for the single-poster page.

Identified Code
<div id="mod_poster-content" class="mod_poster-content {{#editing}}mod_poster-content-editing{{/editing}} {{classes}}">
Suggested Fix

Make the IDs unique per instance, e.g. pass the cmid and append it, or use {{uniqid}}, and target the classes in CSS and tests:

<div id="mod_poster-content-{{cmid}}" class="mod_poster-content ...">
...
<div class="mod_poster-content-region mod_poster-content-region-{{suffix}}" id="mod_poster-content-{{cmid}}-region-{{suffix}}">
Identified Code
<div class="mod_poster-content-region" id="mod_poster-content-region-{{suffix}}">
code qualityLow
JS that blocks register is dropped when posters render on the course page

For inline posters, poster_cm_info_view() creates a fresh moodle_page and loads and renders the poster's blocks against it. Core gives each moodle_page its own page_requirements_manager (moodle_page::magic_get_requires()). Any AMD modules, JS calls or strings that a block registers through $this->page->requires are therefore collected on the throwaway page. They are never output with the course page. Blocks that need client-side JavaScript can render on the course page without working interactivity, even though they work on the poster's own page.

Risk Assessment

Low risk. This is a functional limitation of the inline display mode with no security impact. Simple HTML or text blocks are unaffected.

Context

poster_cm_info_view() runs while cm_info builds its view data, typically while the course page renders. The blocks belong to the module context and the mod-poster-view page type, so the plugin builds a second page object in order to load them.

Identified Code
$page = new moodle_page();
$page->set_context($cm->context);
$page->set_cm($cm);
$poster->setup_page($page);
$page->blocks->load_blocks();
$content .= $page->get_renderer('mod_poster')->view_page_content($poster->get_settings());
Suggested Fix

Document that interactive blocks may not work inline. Alternatively, after rendering, copy the separate page's JS requirements into the global $PAGE->requires, or render the inline content through a mechanism that shares the current page's requirements manager.

Additional AI Notes

Block cleanup on deletion is handled by core. poster_delete_instance() only removes the poster row. Block instances in the module context are removed when core deletes the module context: context::delete_content() calls blocks_delete_all_for_context(). No db/uninstall.php is needed.

Inline rendering cost. Each inline poster shown on the course page runs a poster record query, get_course() and a full load_blocks() on every course page view, because cm_info_view content is not cached. This is acceptable for a few posters but worth keeping in mind for courses with many inline posters.

This review was generated by an AI system and may contain inaccuracies. Findings should be verified by a human reviewer before acting on them.