Custom home pages
tool_muhome
Custom home pages plugin (tool_muhome) for Moodle, part of the MuTMS suite. Allows administrators and managers to create, configure, and manage custom home pages with visibility controls based on cohorts, guest access, date restrictions, and multi-tenancy support. Pages can optionally replace the default Moodle "Home" menu and support block-based content via Moodle's block system. Requires the `tool_mulib` dependency plugin.
The plugin demonstrates strong security practices throughout. All management pages enforce require_login() and require_capability() checks with appropriate capabilities (tool/muhome:manage for write operations, tool/muhome:view for read operations). Forms extend a moodleform-based class (tool_mulib\local\ajax_form) which handles CSRF/sesskey protection automatically. All user input is obtained through required_param() / optional_param() with proper PARAM_* type validation. Output is consistently sanitized using format_string(). Database operations use the $DB API with parameterized queries.
The only finding is a minor code quality issue: a $now = time() variable is interpolated directly into a SQL string rather than using a named parameter. Since time() always returns an integer, this carries no security risk but deviates from best practice.
The plugin properly implements the Privacy API (null_provider), has comprehensive test coverage (PHPUnit and Behat), handles edge cases like missing contexts gracefully, and bundles no third-party libraries. Web services validate parameters and enforce capabilities. The report builder integration follows standard Moodle patterns.
Plugin Overview
tool_muhome is a custom home pages plugin that allows managers to create block-based landing pages with sophisticated visibility controls. Key features:
- Page management via a report builder interface with create, update, move, and delete operations
- Visibility controls: guest access, all-user access, cohort-based access, date-range restrictions
- Multi-tenancy support via integration with
tool_mutenancy - Homepage replacement: can optionally replace the default Moodle "Home" menu item
- Navigation integration: adds custom pages to the primary navigation menu
Security Assessment
The plugin is well-secured:
- All management pages enforce
require_login()andrequire_capability('tool/muhome:manage', $context) - The public-facing
index.phpcorrectly handles guest access when configured, and falls back to login + capability checks for restricted pages - Forms use the moodleform framework (via
tool_mulib\local\ajax_form) for automatic CSRF protection - Web services validate parameters and check capabilities before executing
- All database queries use the
$DBAPI with proper parameterization - All output is sanitized with
format_string()or rendered through Moodle's output API
Code Quality
The code is clean, well-organized, and follows Moodle conventions. One minor deviation: a time() return value is interpolated directly into SQL rather than using a named parameter. This is safe but inconsistent with the parameterized query pattern used everywhere else.
Architecture Notes
The plugin depends on tool_mulib for shared utilities (SQL builder, AJAX forms, context mapping, output helpers). The forms (page_create, page_update, page_delete, page_move) are rendered as AJAX dialogs. The report builder system report provides the management listing with proper context-scoped filtering.
Findings
In the get_my_pages() method, the $now variable (set to time()) is interpolated directly into the SQL string via PHP double-quoted string syntax, rather than using a named parameter placeholder.
While time() always returns an integer, making this safe from SQL injection, it deviates from the consistent use of parameterized queries seen throughout the rest of the plugin. Best practice in Moodle is to use named parameters (:now) for all values in SQL, even when the value is guaranteed to be safe.
Low risk. The $now variable is assigned from PHP's time() function which unconditionally returns an integer. There is no code path where $now could contain non-integer or user-controlled data. The interpolation is therefore safe from SQL injection. This is purely a code quality and consistency concern — the rest of the plugin consistently uses parameterized queries, and this is the only exception.
The get_my_pages() method fetches the list of visible custom home pages for the current user. It constructs a SQL query that filters pages by status, date visibility windows (hiddenbefore / hiddenafter), user/guest visibility, and optionally tenant restrictions. The $now variable holds the current Unix timestamp from time() and is used to compare against the page's date-based visibility fields.
$now = time();
$sql = new sql(
"SELECT p.id, p.name
FROM {tool_muhome_page} p
JOIN {context} ctx ON ctx.id = p.contextid
WHERE p.status = :active
AND (p.hiddenbefore IS NULL OR p.hiddenbefore <= $now)
AND (p.hiddenafter IS NULL OR p.hiddenafter > $now)
/* userwhere */ /* tenantwhere */
ORDER BY p.priority DESC, p.id ASC",
['active' => self::STATUS_ACTIVE]
);
Use named parameters for the timestamp values:
$now = time();
$sql = new sql(
"SELECT p.id, p.name
FROM {tool_muhome_page} p
JOIN {context} ctx ON ctx.id = p.contextid
WHERE p.status = :active
AND (p.hiddenbefore IS NULL OR p.hiddenbefore <= :now1)
AND (p.hiddenafter IS NULL OR p.hiddenafter > :now2)
/* userwhere */ /* tenantwhere */
ORDER BY p.priority DESC, p.id ASC",
['active' => self::STATUS_ACTIVE, 'now1' => $now, 'now2' => $now]
);
The plugin has a well-designed access control model with two capabilities: tool/muhome:view (read, contextlevel CONTEXT_COURSECAT, default for managers) for viewing page details and management, and tool/muhome:manage (write, contextlevel CONTEXT_COURSECAT, default for managers) for creating, updating, moving, and deleting pages. All management endpoints consistently enforce these capabilities in the correct context.
The plugin correctly handles the public-facing index.php page: it respects $CFG->forcelogin, checks $CFG->maintenance_enabled, and conditionally requires login based on whether the requested page is in the user's visible pages list. Pages not in the user's list require login and tool/muhome:view capability.
The Privacy API implementation as null_provider is appropriate. The plugin's database tables (tool_muhome_page and tool_muhome_page_cohortvisible) store page configuration and cohort-to-page mappings — no personal user data is stored.
The plugin includes both PHPUnit and Behat tests, with test data generators for both unit and acceptance testing. The test coverage includes page management operations, callbacks, external services, and user/guest/tenant scenarios.
The plugin's homepage replacement mechanism (hook_after_config) is carefully implemented. It temporarily overrides $CFG->defaulthomepage during the request and restores the original value when the user logs in via the user_loggedin event handler. The redirect logic in hook_after_config is limited to /index.php to avoid interfering with other pages.