Poster
mod_poster
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.
The review found no security vulnerabilities. Every entry point is protected with the correct core controls:
view.phpcallsrequire_course_login()with the course module andrequire_capability('mod/poster:view').- The editing-mode toggle checks
confirm_sesskey()anduser_allowed_editing(). index.phpcallsrequire_course_login().- Inline rendering on the course page checks
uservisibleand the view capability. The core block manager enforcesmoodle/block:viewfor each block. - All database access goes through parameterised
$DBcalls. Output is escaped throughformat_string()andformat_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.
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
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.
Low risk. This is a markup and accessibility defect with no security impact. It only appears when a course shows several inline posters.
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.
<div id="mod_poster-content" class="mod_poster-content {{#editing}}mod_poster-content-editing{{/editing}} {{classes}}">
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}}">
<div class="mod_poster-content-region" id="mod_poster-content-region-{{suffix}}">
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.
Low risk. This is a functional limitation of the inline display mode with no security impact. Simple HTML or text blocks are unaffected.
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.
$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());
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.
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.