One admin panel for many sites. Plain PHP, MySQL and GD. No framework, no Composer, no build step.
Everything a blog needs, nothing it doesn't. The whole thing is readable in an afternoon.
The admin lives in dovecore. Each website is a small dovesite folder with its own pages and config.
Categories go as deep as you like and URLs follow the tree: /travel/italy/my-post.
Any number of extra fields per article, plus generated or hand-written meta description and keywords.
Uploads are validated, re-encoded by GD, resized to large / medium / thumb and stripped of metadata.
Debug off shows a clean error page and logs the details. Debug on shows the real error, file and stack trace.
DDEV exports the database on stop and imports it on start, so a project travels between servers as files.
The URL is split on /. The longest leading part that matches a controller wins, and the rest become parameters. The site folder is checked first, then the core.
Folders
/home/you/ dovecore/ # shared CMS + admin panel core.php libraries/ control/ view/ assets/ dovesite/ # this website control/ view/ storage/ database/ public_html/ # web root index.php # settings live here
Settings in public_html/index.php
const DEBUG = 'off'; // 'on' shows real errors const DOVE_VARS = [ 'analytics_id' => 'G-XXXX', ]; // anywhere: dove_var('analytics_id'); const DOVE_CONFIG = [ 'db' => [ 'host' => 'localhost', ... ], 'site_name' => 'My Blog', ];
Routing
| URL | Controller | Where |
|---|---|---|
/ | control/home.php | site |
/travel/italy/my-post | control/_content.php (category / article tree) | site |
/login | control/login.php | core |
/admin/article/12 | control/admin/article.php, params ['12'] | core |
A site can override any core file (a view, a controller, a library) by using the same relative path in dovesite.
Works on shared hosting. Upload three folders side by side.
dovecore, dovesite and public_html next to each other. Only public_html is web accessible.DOVE_CONFIG in public_html/index.php.dovesite/storage and public_html/media writable by PHP./install, choose a username and password. Then use /admin.Five wrong passwords block the IP for three hours. Passwords use password_hash; sessions are HttpOnly and SameSite.
Every POST is CSRF-checked by the router. All SQL is prepared; output is escaped.
Article HTML goes through a whitelist sanitizer. Uploads are re-encoded and scripts are blocked in media/.