Architecture

Manager System (backend classes in lib/)

  • PluginManager — handles plugin discovery, enable/disable, hook registration/execution (actions, filters, checks)
  • ThemeManager — handles theme discovery, activation, and CSS URL/path resolution
  • UpdateManager — handles version tracking, update checks, and backup/recovery for core, plugins, and themes
  • Migrator — file-based database migrations with locking, transactional execution, and batch rollbacks
  • AuthZ — centralized authorization service (role-based permissions, ownership checks, can:permission middleware)

All managers are instantiated in index.php (after the bootstrap) and are fully integrated into the routing layer and admin panel. The Migrator is also used by the CLI (bb.php).

Data Layer (lib/DbQuery.php)

Lightweight query builder over PDO/BbPdo. No ORM, no magic — just sugar over prepared statements:

$db = new DbQuery($pdo);
$user = $db->table('users')->where('id', 42)->first();
$threads = $db->table('threads')->where('status', 'visible')->orderBy('created_at', 'DESC')->limit(10)->get();
$db->table('users')->insert(['username' => 'alice', 'email' => 'a@b.com']);
$db->table('users')->where('id', 42)->update(['email' => 'new@b.com']);
$db->table('users')->where('status', 'banned')->delete();
$page = $db->table('threads')->where('category_id', 5)->paginate(15, $page);

Supports SQLite and MySQL transparently via BbPdo. All core data operations use this layer.

Rendering Layer (src/Renderer.php)

Micro template renderer that separates logic from presentation:

$r = new Bulletin\Renderer(__DIR__ . '/views');
$r->display('thread-clean', ['thread' => $thread, 'posts' => $posts]);

Template helpers: $this->e() (escaped output), $this->partial(), $this->renderComponent(), $this->slot()/$this->yield() (layouts), $this->csrfField(), $this->when(), $this->each().

Layered Structure (no framework, zero dependencies)

The application is still a single upload with no Composer, no Docker, no build step — but the old single index.php has been split into small, focused files under src/:

  • index.php — the thin front controller. It wires the bootstrap, database, managers, registers all routes and dispatches the request through Bulletin\Router.
  • src/Security.php — security helpers: CSRF protection, rate limiting, input validation, security logging
  • src/Response.php — Bulletin\Response — HTTP response value object with html(), json(), redirect(), error() factory methods
  • src/Errors.php — typed HTTP exceptions (UnauthorizedException, ForbiddenException, NotFoundException, ValidationException, ConflictException, TooManyRequestsException, MethodNotAllowedException)
  • src/bootstrap.php — install check, config.json load, i18n setup, PSR-4 autoloader (delegates to TrustedProxies.php and session_setup.php)
  • src/helpers.php — loads helper modules from src/Helpers/ and remaining helpers (base_url(), redirect())
  • src/Helpers/Url.php — URL generation (url(), slugify(), current_route_action())
  • src/Helpers/AuthHelpers.php — auth helpers (is_logged_in(), is_admin(), can_view_thread(), validate_password_strength()). Permission checks go through the AuthZ service; the legacy user_has_permission() helper was removed in 0.9.0.
  • src/Helpers/Upload.php — upload validation (validate_upload(), get_uploaded_images())
  • src/Helpers/Mail.php — email sending (send_email() via SMTP or PHP mail)
  • src/Helpers/Notifications.php — notification helpers (notify_thread_reply(), notify_admin_new_user(), notify_mentioned_users(), create_notification())
  • src/Helpers/Text.php — text/content helpers (escape(), validate_input(), clean_text(), time_ago(), compact_number(), excerpt(), marked_parse())
  • src/Helpers/Avatar.php — avatar rendering (avatar_initial(), avatar_color(), render_avatar())
  • src/Helpers/Data.php — data fetching (sidebar_categories(), forum_statistics(), thread_sort_options(), fetch_threads())
  • src/TrustedProxies.php — trusted proxy detection for correct client IP behind reverse proxies
  • src/session_setup.php — session configuration and hardening
  • src/Router.php — Bulletin\Router — middleware-enabled request router with route groups, named parameters, middleware pipeline, can: permission middleware, and top-level HttpException catching.
  • src/Request.php — Bulletin\Request — centralized input sanitization (get/post/input/has/raw) with typed getters (string/int/bool/email/enum).
  • src/Renderer.php — Bulletin\Renderer — micro template engine for clean view rendering.
  • src/setup.php — ensures directories exist and initialises the database (SQLite/MySQL schema, defaults).
  • src/actions/ — split action handlers:
    • admin.php — dispatcher that includes modular admin handlers:
      • admin/settings.php — site settings, SMTP, image upload
      • admin/moderation.php — thread/post moderation, split/merge
      • admin/users.php — user management, roles, create/edit/delete
      • admin/categories.php — category CRUD and ordering
      • admin/langs.php — language management
      • admin/diagnostics.php — system diagnostics
      • admin/plugins.php — plugin management
      • admin/themes.php — theme management
      • admin/catalog.php — extension catalog
      • admin/updates.php — core/extension updates
    • posts.php — dispatcher for post/thread actions (loads posts-thread.php, posts-new.php, posts-edit.php)
    • posts-thread.php — thread view, watch, unwatch, image upload
    • posts-new.php — new thread creation
    • posts-edit.php — reply, edit post, delete post, edit thread, delete thread
    • users.php — login, register, profile, password reset
    • content.php — categories, search, download
    • misc.php — markdown preview, mention autocomplete

Key traits that remain unchanged:

  • SQLite by default, MySQL configurable via config.json
  • Session-based authentication
  • SEO-friendly URLs via .htaccess rewrite rules

SEO-Friendly URLs

Clean URLs are supported via .htaccess rewrite rules. All requests are routed to index.php where the Bulletin\Router matches the path against registered routes:

  • /thread/{id}-{slug} — single thread view
  • /category/{id}-{slug} — category view
  • /u/{username} — user profile
  • /admin/* — admin panel routes

Routing with Middleware (src/Router.php)

The Bulletin\Router class handles all request dispatch through a middleware pipeline. Routes are registered in index.php with get(), post(), etc., and organized into groups with middleware.

The router supports automatic content negotiation — requests with Accept: application/json or paths under /api/* receive JSON responses automatically.

Middleware Mode Example

use Bulletin\Router;

$router = new Router();

// Apply middleware to a group
$router->middleware('auth')->group(function($router) {
    $router->get('/thread/{id:\d+}', fn($p) => handle_thread($p['id']));
    $router->post('/thread/{id:\d+}/reply', fn($p) => handle_reply($p['id']));
});

// API routes
$router->api()->middleware('auth', 'csrf')->post('/api/threads', 'api_create_thread');

// Custom middleware
$router->registerMiddleware('rate_limit', function($params) {
    if (rate_limited()) return ['status' => 429, 'body' => 'Too many requests'];
    return null;
});

$router->dispatch();

API Routes

The router automatically detects JSON requests and sets the appropriate Content-Type header:

// Automatic JSON response for API routes
$router->api()->get('/api/threads', function($params) {
    return ['threads' => fetch_threads()];  // Auto-encoded to JSON
});

// Or detect via Accept header
$router->get('/data', function($params) {
    return ['key' => 'value'];  // JSON if Accept: application/json
});
Middleware Purpose
guest Redirect logged-in users away (for login/register pages)
auth Require authentication, redirect to login if missing
admin Require admin.access permission via AuthZ, 403 if unauthorized
csrf Validate CSRF token on POST requests
can:permission Require a specific permission via AuthZ (e.g., can:moderation.manage)

Route Parameters

Named parameters with optional type constraints:

$router->get('/thread/{id:\d+}', $handler);    // digits only
$router->get('/user/{name}', $handler);          // any non-slash
$router->get('/post/{slug:[a-z0-9-]+}', $handler); // custom regex

Directory Structure

/bulletinbored/
├── config.json            # Configuration (database, email, site, theme, localization)
├── index.php              # Thin front controller (bootstrap + routing only)
├── bb.php                 # CLI entry point (migrate, plugin:list, cache:flush, ...)
├── router.php             # Router for PHP built-in server (dev)
├── .htaccess              # SEO-friendly URL rewrites (Apache/LiteSpeed)
├── nginx.conf             # Nginx server block with rewrite rules and deny rules
├── web.config             # IIS URL Rewrite rules and deny rules
├── VERSION                # Single-source-of-truth version file
├── migrations/            # File-based database migrations (namespaced IDs: core:, plugin:)
│   └── YYYYMMDD_description.php
├── lib/                   # Backend managers and data layer
│   ├── BbPdo.php          # PDO wrapper with SQLite/MySQL SQL normalization
│   ├── DbQuery.php        # Lightweight query builder (table/where/first/insert/update/delete)
│   ├── Migrator.php       # File-based migration engine (up/down, batches, rollback)
│   ├── PluginManager.php  # Plugin manager facade (composed from the traits below)
│   ├── PluginManager/     # Cohesive traits: PluginHooks, PluginManifest, PluginDependencies, PluginPackages
│   ├── PluginDiscovery.php # Plugin folder/legacy-file discovery and manifest parsing
│   ├── PackageInstaller.php # ZIP extraction (Zip Slip safe), flattening, integrity checks
│   ├── ThemeManager.php   # Theme discovery, activation
│   ├── UpdateManager.php  # Version tracking, updates, backup/recovery
│   ├── AuthZ.php          # Authorization service (role-based permissions, ownership)
│   └── repo_install.php   # Repository-based install/upgrade helpers
├── src/                   # Application core (no framework)
│   ├── bootstrap.php      # install check, config, i18n, PSR-4 autoloader
│   ├── Security.php       # CSRF, rate limiting, input validation, security logging
│   ├── Response.php       # Bulletin\Response — HTTP response value object
│   ├── Errors.php         # Typed HTTP exceptions
│   ├── TrustedProxies.php # Trusted proxy detection (IPv4/IPv6/CIDR)
│   ├── session_setup.php  # Session configuration and hardening
│   ├── App.php            # ApplicationContext — centralized state (replaces $GLOBALS)
│   ├── helpers.php        # loads helper modules from src/Helpers/
│   ├── Helpers/           # modular helper functions
│   │   ├── Url.php        # URL generation, slugify, current_route_action
│   │   ├── AuthHelpers.php # auth helpers, can_view_thread, validate_password_strength
│   │   ├── Upload.php     # upload validation, get_uploaded_images
│   │   ├── Mail.php       # send_email (SMTP + PHP mail)
│   │   ├── Notifications.php # notifications and admin alerts
│   │   ├── Text.php       # escape, validate_input, time_ago, excerpt, marked_parse
│   │   ├── Avatar.php     # avatar_initial, avatar_color, render_avatar
│   │   └── Data.php       # sidebar_categories, forum_statistics, fetch_threads
│   ├── Router.php         # Bulletin\Router — middleware-enabled request router
│   ├── Request.php        # Bulletin\Request — centralized input sanitization
│   ├── Renderer.php       # Bulletin\Renderer — micro template engine
│   ├── setup.php          # directory checks + database initialisation (via Migrator)
│   ├── actions/           # split action handlers
│   │   ├── admin.php      # admin panel, settings, updates, catalog
│   │   ├── posts.php      # dispatcher (loads posts-thread, posts-new, posts-edit)
│   │   ├── posts-thread.php # thread view, watch, unwatch, image upload
│   │   ├── posts-new.php  # new thread creation
│   │   ├── posts-edit.php # reply, edit, delete post/thread
│   │   ├── users.php      # login, register, profile, password reset
│   │   ├── content.php    # categories, search, download
│   │   └── misc.php       # markdown preview, mention autocomplete
├── tests/                 # Zero-dependency test suite (346 core test functions / 1242 assertions)
│   ├── harness.php        # Test + TestSuite classes (the engine)
│   ├── run.php            # CLI runner
│   ├── DbQueryTest.php    # Query builder tests
│   ├── E2eFlowTest.php    # End-to-end flow tests (thread lifecycle, JSON API, plugins)
│   ├── PluginManagerTest.php # Hook system + manifest validation tests
│   ├── AuthTest.php       # Auth, permissions, CSRF, AuthZ tests
│   ├── MigratorTest.php   # Migration engine tests
│   ├── SecurityTest.php   # CSRF rotation, Request, audit log, trusted proxies tests
│   ├── ResponseTest.php   # Response object + typed Request tests
│   ├── MarkdownTest.php   # Markdown security tests (XSS, URL schemes)
│   └── PluginRouterTest.php # Plugin route/middleware registration tests
├── views/                 # Template files
│   ├── header.php         # Shared frontend header/footer (loads theme CSS)
│   ├── thread-clean.php   # Clean thread view using components
│   ├── components/        # Reusable UI components
│   │   ├── post.php       # Single post component
│   │   └── thread_modals.php # Moderation modals
│   ├── partials/          # Legacy partials (sidebar, thread_list)
│   └── ...                # Other view templates
├── themes/                # Theme system (like plugins)
│   └── freshbored/
│       └── style.css      # Default theme styles
├── plugins/               # Plugin system
├── uploads/               # File upload storage (auto-created)
│   └── avatars/           # User avatar uploads
├── data/                  # SQLite database storage (auto-created)
│   ├── .htaccess          # blocks direct access to database/config files
│   ├── logs/              # Admin audit log (security.log)
│   └── ratelimit/         # Rate limiter buckets (auto-created)
├── lang/                  # Localization files (JSON only — never PHP)
│   └── en.json            # English translations (add your own <code>.json</code> here)
└── README.md

Theme System

Themes work like plugins — each theme is a folder in themes/ with a style.css file:

  • Configure active theme in config.json: "theme": "freshbored"
  • Create custom themes by adding folders in themes/
  • Theme CSS is automatically loaded by views/header.php
  • All frontend pages use the active theme
  • Admin panel uses Bootstrap 5 default styles

Plugin System

Plugins are PHP files in plugins/ that define an {name}_init() function:

  • $pluginManager->addHook('event', $callback) — register a hook
  • $pluginManager->runHook('event', ...$args) — fire a hook
  • Plugins are auto-loaded on every request

Example plugin in plugins/hellobored/hellobored.php demonstrates the pattern.