Action Handlers

Updated: 2026-09-25 (version 0.9.0)

Core request handlers live in src/actions/. The top-level dispatcher files
(posts.php, users.php, content.php, misc.php, admin.php) are thin and
include the focused modules. Every handler now returns a Bulletin\Response
(or bool for legacy dispatching) instead of echoing and calling die()/exit();
the router sends the response.

Module Lines Responsibility
src/actions/admin.php ~124 Admin dispatcher + dashboard.
src/actions/admin/settings.php ~145 Site settings, SMTP, image upload.
src/actions/admin/moderation.php ~225 Moderate, front-moderate, split, merge.
src/actions/admin/users.php ~212 Roles, user CRUD, ban/unban/suspend.
src/actions/admin/categories.php ~75 Category CRUD and ordering.
src/actions/admin/langs.php ~190 Language management.
src/actions/admin/plugins.php ~100 Plugin management.
src/actions/admin/themes.php ~89 Theme management.
src/actions/admin/catalog.php ~127 Extension catalog.
src/actions/admin/updates.php ~101 Core/extension updates.
src/actions/admin/diagnostics.php ~66 System diagnostics.
src/actions/posts.php ~39 Posts dispatcher.
src/actions/posts-thread.php ~230 Thread view, watch/unwatch, image upload.
src/actions/posts-new.php ~89 New thread creation.
src/actions/posts-edit.php ~284 Reply, edit/delete post, edit/delete thread.
src/actions/users.php ~471 Login, register, verify, profile, password reset.
src/actions/content.php ~101 Search, category view, download.
src/actions/misc.php ~41 Markdown preview, mention autocomplete.

Handler inventory

Posts / threads (posts-thread.php, posts-new.php, posts-edit.php)

Function Signature
handle_thread_view() (array $params = []): Response
handle_upload_image() (): Response
handle_watch() / handle_unwatch() (): Response
handle_new_thread() (string $method): Response
handle_reply_post() (): Response
handle_edit_post() (string $method, array $params = []): Response
handle_edit_thread() (string $method, array $params = []): Response
handle_delete_post() (array $params = []): Response
handle_delete_thread() (array $params = []): Response

Users (users.php)

Function Signature
handle_login() (string $method): Response
handle_register() (string $method): Response
handle_logout() (): Response
handle_verify_email() (): Response
handle_profile() (array $params = []): Response
handle_edit_profile() (string $method): Response
handle_remove_avatar() (string $method): Response
handle_forgot_password() (string $method): Response
handle_reset_password() (string $method): Response

Content & misc (content.php, misc.php)

Function Signature
handle_search() (): Response
handle_category() (array $params = []): Response
handle_download() (array $params = []): Response
handle_markdown_preview() (): Response
handle_mention_users() (): Response

Admin (admin.php + admin/*)

Function Signature
handle_admin_dashboard() (): Response
handle_admin_settings_get() / handle_admin_settings_post() (): Response / (): ?string
handle_admin_smtp_get() / handle_admin_smtp_post() (): Response / (): ?string
handle_admin_upload_site_image() / handle_admin_get_images() (): Response
handle_admin_moderation_get() (): Response
handle_moderate_post() (array $params = []): Response
handle_frontend_moderate_post() (): Response
handle_split_thread_post() / handle_merge_thread_post() (): Response
handle_admin_roles_get() / handle_admin_roles_action_post() (): Response
handle_admin_users_get() / handle_admin_user_edit() (): Response / (string $method, array $params = []): Response
handle_admin_create_user_post() / handle_delete_user_post() (): Response
handle_admin_ban_user_post() / handle_unban_user_post() / handle_suspend_user_post() (): Response
handle_admin_categories() / handle_delete_category_post() / handle_update_category_order_post() (): Response
handle_admin_langs() (string $method)
handle_admin_diagnostics_get() (): Response
handle_admin_plugins() / handle_admin_themes() (string $method): Response
handle_admin_catalog() (string $method): Response
handle_admin_updates() (string $method): Response

Protections

Every state-changing handler applies CSRF validation and (since 0.9.0) a rate
limit. Sensitive actions fail closed when the limiter cannot operate. The
rate-limit actions and windows are listed in
Configuration → Rate Limiting.

Operation CSRF Auth Rate limit Authorization
Login / register / password reset yes guest yes —
Create thread / reply yes auth yes category role check
Edit / delete post / thread yes auth yes owner or AuthZ (admin/mod)
Upload image / edit profile / remove avatar yes auth yes self
Watch / unwatch yes auth yes self
Admin settings / user / category / plugin / theme / update actions yes admin yes admin middleware
Moderate / split / merge yes auth yes can:moderation.manage

History

Earlier versions used 49 die() and 3 exit() calls in the handlers and
returned bool from every routing callback. That legacy control flow has been
removed; only Response-based returns (and a single binary-download path) remain.
See the historical Phase 1 Audit for the original findings.