Install
$ agentstack add skill-saadrahman01-claude-moodle-dev-moodle-hooks-api ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Moodle Hooks API
Overview
Moodle 4.4 introduced a typed Hooks API (core\hook\manager) replacing the unmaintainable jungle of magic callback functions like _extend_navigation, _before_http_headers, _extend_settings_navigation, etc. Hooks are real classes with typed payloads, dispatched through \core\di::get(\core\hook\manager::class). Plugins register interest via db/hooks.php.
When to Use
- Adding cross-cutting behavior triggered by core (navigation, page output, user login, course events) without monkey-patching
- Migrating a plugin off legacy magic callbacks (deprecated 4.4+, will be removed)
- Authoring a hook class in core or in a plugin that other plugins can listen to
- Writing tests for hook listeners
Skip when: the event you care about is a \core\event\* (Events 2 API — different system, used for audit/logging). Hooks are for modifying behavior; Events are for reacting to facts.
Core concepts
| Concept | Where it lives | Purpose | |---|---|---| | Hook class | classes/hook/.php | Typed payload, optional setters for listeners to mutate | | Listener registration | db/hooks.php | Maps hook class -> callback (Class::method) + priority | | Dispatcher call | Core or plugin code | \core\di::get(\core\hook\manager::class)->dispatch(new \plugin\hook\thing(...)); | | Listener method | Any class | Static or instance method taking the hook instance |
Listening to a core hook
db/hooks.php:
\core\hook\output\before_standard_top_of_body_html_generation::class,
'callback' => \local_example\hook_listener::class . '::inject_banner',
'priority' => 100, // higher runs first
],
];
classes/hook_listener.php:
add_html('Hello, ' . s($USER->firstname) . '');
}
}
After adding or changing db/hooks.php, purge caches: php admin/cli/purge_caches.php.
Authoring your own hook
classes/hook/before_widget_render.php:
html .= $html; }
public function get_html(): string { return $this->html; }
public function stop(): void { $this->stopped = true; }
public function isPropagationStopped(): bool { return $this->stopped; }
}
Dispatch:
$hook = new \local_example\hook\before_widget_render($widgetid, $context);
\core\di::get(\core\hook\manager::class)->dispatch($hook);
if ($hook->isPropagationStopped()) {
return ''; // veto
}
echo $hook->get_html();
Migrating magic callbacks
| Legacy callback | Replacement hook | |---|---| | _extend_navigation | \core\hook\navigation\primary_extend (4.5+) — check core for current name | | _before_http_headers | \core\hook\output\before_http_headers | | _before_standard_top_of_body_html | \core\hook\output\before_standard_top_of_body_html_generation | | _before_footer | \core\hook\output\before_footer_html_generation | | _after_config | \core\hook\after_config | | _extend_settings_navigation | check \core\hook\navigation\* for current name |
Migration steps:
- Search for legacy callbacks:
grep -rn "function.*_extend_navigation\|_before_http_headers\|_after_config" . - For each, find the matching hook class in
lib/classes/hook/of your Moodle install. - Create
db/hooks.phpmapping; move the callback body into a listener class. - Delete the legacy function from
lib.php. - Bump
version.php, purge caches, run tests.
Testing hook listeners
resetAfterTest();
$this->setUser($this->getDataGenerator()->create_user());
$hook = new \core\hook\output\before_standard_top_of_body_html_generation();
\core\di::get(\core\hook\manager::class)->dispatch($hook);
$this->assertStringContainsString('Hello,', $hook->get_output());
}
public function test_skipped_for_guest(): void {
$this->resetAfterTest();
$this->setGuestUser();
$hook = new \core\hook\output\before_standard_top_of_body_html_generation();
\core\di::get(\core\hook\manager::class)->dispatch($hook);
$this->assertStringNotContainsString('Hello,', $hook->get_output());
}
}
CLI: list registered hooks
php admin/cli/hooks_list.php # all hooks + listeners
php admin/cli/hooks_list.php --hook=core\\hook\\output\\before_http_headers
Gotchas
- Purge caches after any
db/hooks.phpchange. Listener registration is cached. - Priority is a hint — order between equal priorities is undefined. Don't rely on it for correctness.
- Stoppable hooks: only implement
stoppable_event_interfaceif vetoing is meaningful. Most output hooks are not stoppable. - Don't dispatch hooks from constructors or
setUp()— they can have side effects. - DI container: always resolve
managervia\core\di::get(...). Don'tnewit. - Backporting: pre-4.4 plugins still need legacy callbacks. Either keep both with a version check, or drop pre-4.4 support and bump
requiresinversion.php.
Checklist
- [ ]
db/hooks.phpexists with correcthookclass FQCN - [ ] Listener class is autoloadable (under
classes/with PSR-4) - [ ] Caches purged after edit
- [ ] Tests cover both the action and the no-op branch
- [ ] Legacy callback removed (or version-gated) after migration
- [ ]
version.phpbumped
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: SaadRahman01
- Source: SaadRahman01/claude-moodle-dev
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.