DraftVertex CMS
Lightweight, modular PHP CMS with a WordPress-inspired hooks & filters architecture
PHP required
8.1+
Database
MySQL / SQLite
Architecture
Hook-driven
System attributes
| Attribute | Value |
|---|---|
| Version | 1.0.0 |
| DB version | 1 |
| DB prefix | dv_ |
| Front controller | index.php → bootstrap.php → router.php |
| Template system | Layout-based with fallback hierarchy |
| Admin slug | /dv-admin |
| API base | /dv-api |
| Function prefix | dv_ |
Boot order
index.php
└── dv-includes/bootstrap.php
├── hooks.php # action/filter engine
├── db.php # PDO wrapper
├── options.php # key/value site settings
├── session.php # secure sessions + CSRF
├── permissions.php # RBAC
├── enqueue.php # scripts/styles
├── extensions # load active extensions
├── layouts # load active layout functions.php
└── router.php → template
File structure
Complete directory layout of a DraftVertex installation
Root
dv-cms/ ├── .htaccess # Rewrite rules (managed via Permalink settings) ├── index.php # Front-end entry point ├── ajax.php # AJAX handler entry point ├── dv-config.php # Database, keys, paths, debug settings ├── dv-config-sample.php # Sample config (copy to dv-config.php) ├── sitemap.xsl └── src/ ├── dv-admin/ # Admin dashboard pages ├── dv-includes/ # Core engine files ├── dv-setup/ # Installation wizard └── dv-content/ # User-generated content ├── cache/ ├── uploads/ ├── layouts/ │ ├── layout-101/ │ ├── modern-alpha/ │ └── draftvertex-classic/ └── extensions/ └── seo-engine/
dv-includes/ (engines)
bootstrap.php
hooks.php
db.php
options.php
session.php
permissions.php
router.php
enqueue.php
rest-api.php
post-types.php
taxonomies.php
cache.php
cron-engine.php
mail-engine.php
media-engine.php
comments-engine.php
canvas-builder.php
visual-engine.php
layout-engine.php
extension-engine.php
menus-engine.php
widgets-engine.php
shortcodes.php
site-health-engine.php
sitemaps.php
template-tags.php
ajax-handler.php
recovery.php
class-dv-sanitizer.php
class-dv-list-table.php
mailer/class-dv-smtp.php
hooks.php
db.php
options.php
session.php
permissions.php
router.php
enqueue.php
rest-api.php
post-types.php
taxonomies.php
cache.php
cron-engine.php
mail-engine.php
media-engine.php
comments-engine.php
canvas-builder.php
visual-engine.php
layout-engine.php
extension-engine.php
menus-engine.php
widgets-engine.php
shortcodes.php
site-health-engine.php
sitemaps.php
template-tags.php
ajax-handler.php
recovery.php
class-dv-sanitizer.php
class-dv-list-table.php
mailer/class-dv-smtp.php
dv-admin/ (pages)
index.php
auth.php
posts.php
media.php
users.php
categories.php
comments.php
menus.php
components.php
visuals.php
preferences.php
profile.php
extensions.php
extensions-editor.php
extensions-install.php
layouts.php
layouts-editor.php
layouts-install.php
tools.php
upgrade.php
menu-registry.php
partials/
assets/
auth.php
posts.php
media.php
users.php
categories.php
comments.php
menus.php
components.php
visuals.php
preferences.php
profile.php
extensions.php
extensions-editor.php
extensions-install.php
layouts.php
layouts-editor.php
layouts-install.php
tools.php
upgrade.php
menu-registry.php
partials/
assets/
Configuration
Constants defined in
dv-config.php — copy from dv-config-sample.phpAll constants have safe defaults in their engine files. Only define what you need to override.
Database
| Constant | Default | Description |
|---|---|---|
DV_DB_DRIVER | mysql | mysql or sqlite |
DV_DB_HOST | localhost | Database hostname |
DV_DB_NAME | database_name | Database name or SQLite filename |
DV_DB_USER | username | Database username |
DV_DB_PASSWORD | password | Database password |
DV_DB_CHARSET | utf8mb4 | Connection charset |
DV_DB_PREFIX | dv_ | Table prefix (configurable) |
Security keys & salts
| Constant | Description |
|---|---|
DV_AUTH_KEY | Authentication key (random hex) |
DV_SECURE_AUTH_KEY | Secure auth key |
DV_LOGGED_IN_KEY | Logged-in cookie key |
DV_NONCE_KEY | Nonce hashing key |
DV_AUTH_SALT | Auth salt |
DV_NONCE_SALT | Nonce salt |
Paths & URLs
| Constant | Description |
|---|---|
DV_SITE_URL | Public site URL (e.g. http://localhost:8000) |
DV_ADMIN_URL | Admin URL (SITE_URL + /dv-admin) |
DV_ROOT_PATH | Absolute filesystem root (auto-detected) |
DV_SRC_PATH | Path to src/ |
DV_INCLUDES_PATH | Path to dv-includes/ |
DV_ADMIN_PATH | Path to dv-admin/ |
DV_CONTENT_PATH | Path to dv-content/ |
DV_UPLOADS_PATH | Path to uploads/ |
DV_CONTENT_URL | Content URL |
DV_UPLOADS_URL | Uploads URL |
Debug, cache & limits
| Constant | Default | Description |
|---|---|---|
DV_DEBUG | false | Enable error reporting |
DV_DEBUG_LOG | false | Log errors to debug.log |
DV_DEBUG_DISPLAY | false | Display errors on screen |
DV_CACHE | false | Enable page caching |
DV_CACHE_TTL | 3600 | Cache lifetime in seconds |
DV_MAX_UPLOAD_SIZE | 64M | Max upload file size |
DV_ALLOWED_EXTENSIONS | jpg,jpeg,png… | Allowed file types |
DV_DISABLE_CRON | false | Disable built-in cron runner |
DV_SESSION_NAME | dv_session | Session cookie name |
DV_SESSION_LIFETIME | 86400 | Session duration in seconds |
DV_LOGIN_LOCKOUT_ATTEMPTS | 5 | Lockout threshold |
DV_LOGIN_LOCKOUT_DURATION | 900 | Lockout duration in seconds |
DV_ADMIN_SLUG | dv-admin | Admin URL slug |
Installation wizard
5-step guided setup at
dv-setup/install.phpSetup steps
| Step | Purpose |
|---|---|
| 1 — Welcome | Requirements check: PHP 8.1+, PDO extension, mbstring, writable directories |
| 2 — Database | Configure MySQL or SQLite connection and test it |
| 3 — Site info | Site name, public URL, admin email |
| 4 — Admin account | Create administrator username and password |
| 5 — Complete | Success page with links to admin panel and front-end site |
What the installer does
| Action | Detail |
|---|---|
| Runs SQL schema | schema.sql for MySQL, schema_sqlite.sql for SQLite |
| Creates admin user | UPSERT — preserves existing account data |
| Sets site options | name, URL, admin email stored in dv_options |
| Generates dv-config.php | With cryptographically random salts and keys |
| Creates uploads dir | With index.php protection file |
Security: Remove or restrict the
dv-setup/ directory after installation completes.Hooks system
WordPress-compatible action & filter engine —
dv-includes/hooks.phpCore functions
| Function | Purpose |
|---|---|
dv_add_action($hook, $cb, $priority, $args) | Register a callback on an action hook |
dv_do_action($hook, ...$args) | Fire all callbacks on an action hook |
dv_has_action($hook) | Check if a hook has registered callbacks |
dv_remove_action($hook, $cb, $priority) | Remove a callback from an action |
dv_add_filter($hook, $cb, $priority, $args) | Register a filter callback |
dv_apply_filters($hook, $value, ...$args) | Apply filter callbacks to a value and return result |
Actions run code at specific moments. Filters modify data before it is used. Lower priority = earlier execution (default is 10).
Usage examples
// Action: run code when a post is saved dv_add_action('dv_save_post', function($post_id, $post_data) { error_log("Post {$post_id} was saved"); }, 10, 2); // Filter: append a footer note to all content dv_add_filter('dv_the_content', function($content) { return $content . '<p>— Footer note</p>'; }); // Filter: restrict upload size to 2 MB dv_add_filter('dv_max_upload_size', fn() => 2 * 1024 * 1024); // Action: add a custom field to the registration form dv_add_action('dv_register_form_fields', function() { echo '<div class="dv-auth-form-row">'; echo ' <label>Twitter Handle</label>'; echo ' <input type="text" name="twitter">'; echo '</div>'; });
Actions reference
48 built-in action hooks fired across the system — use
dv_add_action() to hook in| Hook name | File | Arguments |
|---|---|---|
dv_loaded | bootstrap.php | — |
dv_init | bootstrap.php | — |
dv_auth_init | auth.php | $action |
dv_before_auth_verify | auth.php | $username, $password |
dv_user_login | auth.php | $user_id, $user |
dv_user_registered | auth.php | $user_id |
dv_user_registered_unverified | auth.php | $user_id |
dv_user_email_verified | auth.php | $user_id |
dv_send_email_verification_email | auth.php | $user, $token |
dv_send_password_reset_email | auth.php | $user, $token |
dv_auth_card_top | auth.php | $action |
dv_auth_form_before | auth.php | $action |
dv_login_form_top | auth.php | — |
dv_login_form_fields | auth.php | — |
dv_register_form_top | auth.php | — |
dv_register_form_fields | auth.php | — |
dv_forget_password_form_fields | auth.php | — |
dv_reset_password_form_fields | auth.php | — |
dv_auth_social_buttons | auth.php | $action |
dv_auth_card_bottom | auth.php | $action |
dv_auth_custom_action_{$action} | auth.php | — |
dv_save_post | posts.php | $post_id, $_POST |
dv_post_updated | posts.php | $post_id, $data |
dv_edit_post_form_main | posts.php | $post |
dv_edit_post_form_sidebar | posts.php | $post |
dv_media_uploaded | media-engine.php | $file_id, $file_data |
dv_media_deleted | media-engine.php | $file_id |
dv_media_edit_before_details | media.php | $file |
dv_media_edit_after_details | media.php | $file |
dv_comment_post | comments-engine.php | $comment_id |
dv_deleted_comment | comments-engine.php | $comment_id |
dv_edited_comment | comments-engine.php | $comment_id |
dv_user_saved | users.php | $user_id, $_POST |
dv_user_edit_form | users.php | $user |
dv_profile_saved | profile.php | $user_id, $_POST |
dv_profile_form | profile.php | $user |
dv_edited_term | categories.php | $term_id, $taxonomy |
dv_created_term | categories.php | $term_id, $taxonomy |
dv_deleted_term | categories.php | $term_id, $taxonomy |
dv_template_redirect | router.php | $resolved |
dv_register_customizer | visual-engine.php | — |
dv_customizer_save_setting | visuals.php | $setting_id, $value |
dv_customizer_published | visuals.php | $settings |
dv_extension_activated | extension-engine.php | $slug |
dv_extension_deactivated | extension-engine.php | $slug |
dv_rest_api_init | rest-api.php | — |
dv_admin_head | dv-header.php | — |
dv_admin_footer | dv-footer.php | — |
dv_head | template-tags.php | — |
dv_footer | template-tags.php | — |
dv_enqueue_scripts | enqueue.php | — |
dv_option_updated | options.php | $key, $value |
dv_registered_post_type | post-types.php | $post_type |
dv_registered_taxonomy | taxonomies.php | $taxonomy |
dv_get_site_health | site-health-engine.php | $results |
dv_htaccess_updated | bootstrap.php | $path, $structure |
Filters reference
36 built-in filter hooks — use
dv_add_filter() to modify values| Filter name | Returns | Description |
|---|---|---|
dv_allow_auth | bool | Allow auth page access |
dv_auth_allowed_actions | array | Allowed auth action list |
dv_auth_user_lookup | array|null | Override user lookup result |
dv_login_redirect | string | Post-login redirect URL |
dv_allow_registration | bool | Allow new user registration |
dv_registration_errors | array | Registration validation errors |
dv_default_user_role | string | Default role for new users |
dv_email_verification_required | bool | Require email verification on register |
dv_auth_notices | array | Custom auth page notices |
dv_login_headerurl | string | Auth page logo URL |
dv_login_body_class | string | Auth page body CSS class |
dv_login_stylesheet | string | Auth page custom CSS URL |
dv_max_upload_size | int | Max upload size in bytes |
dv_allowed_mime_types | array | Allowed MIME types map |
dv_allowed_extensions | array | Allowed file extensions |
dv_pre_handle_media_upload | bool | Pre-upload validation gate |
dv_media_upload_args | array | Upload handling arguments |
dv_media_query_args | array | Media library query args |
dv_media_modal_html | string | Full media modal HTML |
dv_get_avatar | string | User avatar HTML |
dv_mail_args | array | Email args (to, subject, message) |
pre_dv_mail | mixed | Pre-send mail handler / short-circuit |
dv_smtp_config | array | SMTP configuration array |
dv_get_comments | array | Retrieved comments list |
dv_preprocess_comment | array | Comment data before insert |
dv_comment_is_spam | bool | Spam detection result |
dv_comment_form_args | array | Comment form configuration |
dv_customizer_sections | array | Customizer sections list |
dv_customizer_controls | array | Customizer controls list |
dv_registered_canvas_blocks | array | Registered canvas blocks |
dv_user_capabilities | array | User extra capabilities |
dv_page_templates | array | Available page templates |
dv_template | string | Template file path to load |
dv_the_title | string | Post title output |
dv_the_content | string | Post content output |
dv_body_class | array | Body CSS class array |
dv_nav_menu | string | Rendered navigation menu HTML |
dv_registered_widgets | array | Registered widgets |
dv_htaccess_managed_content | string | Rules inside <IfModule> block |
dv_htaccess_content | string | Full .htaccess content |
dv_dashboard_widgets | array | Admin dashboard widget list |
Router & permalinks
URL resolution engine —
dv-includes/router.phpPermalink modes
| Mode | URL format | Description |
|---|---|---|
plain | /?p=123 | Query string only, no rewriting required |
postname | /hello-world | Clean SEO-friendly slugs (recommended) |
custom | /2026/06/hello-world | Custom structure with date/name tags |
Custom structure tags
%year%%monthnum%%day%
%postname%%post_id%
%category%%author%
Template resolution order (single post)
| # | Path checked (first match wins) |
|---|---|
| 1 | layouts/{active}/templates/single-{post-type}.php |
| 2 | layouts/{active}/templates/single.php |
| 3 | layouts/{active}/single.php |
| 4 | layouts/{active}/templates/archive.php |
| 5 | layouts/{active}/templates/index.php |
| 6 | layouts/{active}/index.php |
| 7 | Built-in fallback HTML layout |
Database schema
MySQL 5.7+ or SQLite 3.x — all tables use the
dv_ prefixForeign keys use CASCADE deletes.
dv_metadata is a unified store — distinguish objects via object_type: user / post / term / comment / media.| Table | Key columns | Purpose |
|---|---|---|
dv_options | id, option_key, option_value, autoload | Site-wide key/value settings |
dv_metadata | id, object_type, object_id, meta_key, meta_value | Unified meta store for any object |
dv_users | id, username (UNIQUE), password_hash, email (UNIQUE), display_name, role, status | User accounts |
dv_posts | id, author_id (FK), post_parent, title, slug (UNIQUE per type), content_raw, canvas_layout_json, post_type, status | Posts, pages, custom post types |
dv_categories | id, name, slug, taxonomy, parent, description, count | Taxonomy terms (categories, tags…) |
dv_term_relationships | id, post_id (FK), term_id (FK) | Post ↔ term associations |
dv_comments | id, post_id (FK), user_id, guest_name, guest_email, content, status | Post comments |
dv_media | id, filename, filepath, mime_type, filesize, uploaded_by (FK), alt_text, caption | Media file library |
dv_sessions | id, session_id, user_id (FK), ip_address, user_agent, last_active | Database-backed sessions |
dv_login_attempts | id, ip_address, attempted_at | Lockout tracking |
dv_nav_menus | id, location, items (JSON) | Navigation menus |
dv_widget_assignments | id, sidebar_id, widget_type, widget_args (JSON), sort_order | Sidebar widget assignments |
Caching system
Full page output cache + transient object cache —
dv-includes/cache.phpTransient API
| Function | Description |
|---|---|
dv_set_transient($key, $value, $expiration) | Store data with optional TTL |
dv_get_transient($key) | Retrieve cached data (false if expired) |
dv_delete_transient($key) | Delete a single cached item |
dv_clear_page_cache() | Wipe all cached page HTML files |
Page cache is auto-cleared on post create/update via
dv_post_created. Enable with DV_CACHE = true and tune TTL with DV_CACHE_TTL.Page cache scope
| Logged-out visitors only | Authenticated requests bypass the cache |
| Cache files location | dv-content/cache/ |
| Auto-invalidated on | Post save, post update, option change |
Cron scheduler
Scheduled background events —
dv-includes/cron-engine.phpAPI methods
| Method | Description |
|---|---|
DV_Cron::scheduleEvent($timestamp, $recurrence, $hook, $args) | Schedule a recurring event |
DV_Cron::unscheduleEvent($hook) | Remove a scheduled event |
DV_Cron::runDue() | Execute all currently-due events |
DV_Cron::checkAndRun() | Check and trigger due events (called on each page load) |
Recurrence intervals
| Key | Seconds | Duration |
|---|---|---|
hourly | 3600 | 1 hour |
daily | 86400 | 24 hours |
weekly | 604800 | 7 days |
Disable the built-in cron runner with
DV_DISABLE_CRON = true and use a real server cron job instead for better reliability.Session & CSRF protection
Secure session handling with CSRF nonce tokens —
dv-includes/session.phpCSRF nonce functions
| Function | Description |
|---|---|
dv_create_nonce($action) | Generate a time-limited CSRF nonce token |
dv_verify_nonce($token, $action) | Verify a nonce token — returns bool |
dv_nonce_field($action) | Output a hidden nonce <input> field |
dv_check_nonce($action) | Verify nonce from POST/GET — dies on failure |
Session methods
| Method | Description |
|---|---|
DV_Session::start() | Initialize secure session with hardened cookie settings |
DV_Session::login($user_id, $user) | Authenticate user, regenerate session ID |
DV_Session::logout() | Log out and destroy current session |
DV_Session::destroy() | Fully destroy session and cookies |
Cookie security settings
| HttpOnly | Cookies not accessible via JavaScript |
| SameSite | Lax — prevents CSRF from cross-site links |
| Secure | Enforced automatically when HTTPS detected |
| Session fixation | ID regenerated on every login |
Media engine
File uploads, validation, and media library —
dv-includes/media-engine.phpKey functions
| Function | Description |
|---|---|
dv_handle_media_upload(array $file) | Process an uploaded file and add to media library |
dv_get_media(int $id) | Get a single media item by ID |
dv_get_all_media(array $args) | Query the media library with filters |
dv_delete_media(int $id) | Delete media item and its file on disk |
dv_get_media_url(int $id) | Get public URL of a media item |
dv_media_input($name, $value, $args) | Render a media picker input field |
dv_get_avatar(int $user_id, array $args) | Get user avatar HTML |
Relevant filters
dv_max_upload_size | Override max upload bytes |
dv_allowed_mime_types | Allowed MIME types array |
dv_allowed_extensions | Allowed file extension list |
dv_pre_handle_media_upload | Short-circuit validation (return false to block) |
dv_media_modal_html | Customise media picker modal HTML |
Comments engine
Threaded comments with spam filtering and moderation —
dv-includes/comments-engine.phpKey functions
| Function | Description |
|---|---|
dv_get_comments(int $post_id, array $args) | Get comments for a post |
dv_insert_comment(array $data) | Insert a new comment — returns comment ID |
dv_delete_comment(int $comment_id) | Delete a comment permanently |
dv_update_comment(int $comment_id, array $data) | Update comment content or status |
dv_comment_form(array $args) | Render the comment submission form |
Relevant filters
dv_preprocess_comment | Modify comment data before insert |
dv_comment_is_spam | Return true to mark as spam |
dv_comment_send_notification | Control notification email sending |
dv_comment_form_args | Customise comment form configuration |
Canvas builder
Drag-and-drop block page builder —
dv-includes/canvas-builder.phpBuilt-in blocks
| Block type | Default data fields |
|---|---|
paragraph | content, size, align, color, link, bold, italic |
heading | content, level (h1–h6), align, color |
image | url, caption, alt, align, width, radius |
button | text, link, align, shape, bg_color, text_color |
quote | content, citation, color |
video | content (iframe or URL) |
html | content, enable_style, enable_script |
divider | style, height, color, margin |
Registering a custom block
dv_register_canvas_block('my_block', [
'name' => 'My Block',
'icon' => 'icon-name',
'description' => 'What this block does',
'default_data' => [
'field1' => 'value1',
'field2' => 'value2',
]
]);
Use the dv_registered_canvas_blocks filter to programmatically add or remove blocks from the list.
Visual customizer
Live preview theme settings engine —
dv-includes/visual-engine.phpControl types
| Type | Description |
|---|---|
text | Single-line text input |
textarea | Multi-line text area |
select | Dropdown (requires choices array) |
checkbox | Boolean on/off toggle |
radio | Radio button group |
image | Media upload selector |
select_page | Page dropdown (from dv_posts) |
layout_selector | Installed layout picker |
html | Static HTML display (info/divider) |
Registering sections & controls
dv_add_action('dv_register_customizer', function() {
DV_Visual_Customizer::register_section('my_section', [
'title' => 'My Section',
'priority' => 10,
]);
DV_Visual_Customizer::register_control('my_setting', [
'section' => 'my_section',
'type' => 'text',
'label' => 'My Setting',
'description' => 'Help text for this control',
'default' => 'default value',
'priority' => 10,
]);
});
// Read the value anywhere
$value = dv_get_option('my_setting', 'default');
Extension system
Plugins live in
dv-content/extensions/{slug}/ — managed via Admin → ExtensionsFile structure
dv-content/extensions/my-extension/ ├── my-extension.php # Main file (required — must match folder name) ├── assets/ # CSS, JS, images ├── templates/ # Optional template files └── README.md
Main file header & example
<?php
/**
* Extension Name: My Extension
* Version: 1.0.0
* Description: What this extension does
* Author: Your Name
*/
dv_add_action('dv_init', function() {
// Runs when CMS is fully initialised
});
dv_add_filter('dv_the_content', function($content) {
return $content . '<p>Added by My Extension</p>';
});
Extension engine API
| Method | Description |
|---|---|
DV_ExtensionEngine::getInstalled() | Scan and return all installed extension metadata |
DV_ExtensionEngine::getActive() | Get array of active extension slugs |
DV_ExtensionEngine::loadActive() | Require and execute all active extensions |
DV_ExtensionEngine::activate(string $slug) | Activate an extension by slug |
DV_ExtensionEngine::deactivate(string $slug) | Deactivate an extension by slug |
Extensions have full access to all action and filter hooks. Use
dv_apply_filters() in your own extension to make it extensible by others.Layout system
Themes live in
dv-content/layouts/{slug}/ — managed via Admin → LayoutsFile structure
dv-content/layouts/my-layout/ ├── my-layout.php # Main file with header (required) ├── functions.php # Auto-included on activation ├── style.css # Auto-enqueued on frontend ├── templates/ │ ├── index.php │ ├── single.php │ ├── page.php │ ├── archive.php │ └── full-width.php # Custom page template └── assets/
Layout header + custom template
<?php /** * Layout Name: My Layout * Version: 1.0.0 * Description: A beautiful layout for DraftVertex CMS * Author: Your Name */
// Custom page template (templates/full-width.php) <?php /** * Template Name: Full Width Page */ // Your template code here…
Template tags available in layouts
| Function | Description |
|---|---|
dv_the_title() | Display current post title |
dv_the_content() | Display current post content |
dv_the_excerpt() | Display post excerpt |
dv_the_permalink() | Display post URL |
dv_the_author() | Display post author name |
dv_the_date() | Display post date |
dv_the_thumbnail() | Display featured image |
dv_has_posts() | Check if posts exist in the current loop |
dv_the_post() | Advance to next post in loop |
dv_body_class() | Output body CSS class string |
dv_head() | Output <head> section hooks |
dv_footer() | Output footer hooks |
dv_get_nav_menu($location) | Get rendered navigation HTML for a location |
dv_get_avatar($user_id) | Get user avatar HTML |
dv_get_comments($post_id) | Get comments array for a post |
dv_get_categories($post_id) | Get category terms for a post |
dv_is_logged_in() | Check if current user is authenticated |
REST API
JSON endpoints at
/dv-api/{route} — dv-includes/rest-api.phpBuilt-in endpoints
| Method | Route | Description |
|---|---|---|
| GET | /dv-api/posts | Get all published posts |
| GET | /dv-api/posts/{id} | Get single post by ID |
| GET | /dv-api/pages | Get all published pages |
| GET | /dv-api/options | Site name, tagline, and public URL |
Registering custom routes
dv_add_action('dv_rest_api_init', function() {
DV_REST_API::registerRoute(
'GET',
'my-endpoint',
function() {
return ['status' => 'ok', 'data' => 'Hello from API'];
}
);
// POST route with auth check
DV_REST_API::registerRoute(
'POST',
'my-endpoint',
function() {
dv_require_cap('edit_posts');
$data = json_decode(file_get_contents('php://input'), true);
return ['received' => $data];
}
);
});
Mail engine & SMTP
Email dispatch with optional SMTP —
dv-includes/mail-engine.phpCore functions
| Function | Description |
|---|---|
dv_mail($to, $subject, $message, $headers) | Send an email — returns bool on success |
dv_get_mail_config() | Get current mail / SMTP configuration |
SMTP configuration via filter
dv_add_filter('dv_smtp_config', function($config) {
return [
'host' => 'smtp.gmail.com',
'port' => 587,
'secure' => 'tls', // 'tls' or 'ssl'
'username' => 'user@gmail.com',
'password' => 'app-password',
'from' => 'noreply@example.com',
'from_name' => 'My Site',
];
});
Use the
pre_dv_mail filter to short-circuit email sending (useful for logging or queueing in testing environments).User system & permissions
Role-based access control —
dv-includes/permissions.phpBuilt-in roles
| Role | Capabilities |
|---|---|
| administrator | Full system access — manage options, users, posts, media, layouts, extensions |
| editor | Publish and manage all posts/pages, moderate comments, manage categories |
| Publish and manage own posts, upload media | |
| contributor | Write own posts (cannot publish), no media upload |
| subscriber | Read-only, can manage own profile |
Key functions
| Function | Description |
|---|---|
dv_current_user() | Get current logged-in user data array |
dv_current_user_id() | Get current user ID (0 if guest) |
dv_current_user_role() | Get current user role string |
dv_is_logged_in() | Check if a user is authenticated |
dv_require_login() | Redirect to login page if not authenticated |
dv_require_cap($cap) | Redirect with 403 if user lacks capability |
dv_user_can($user_id, $cap) | Check if a specific user has a capability |
dv_get_user_meta($user_id, $key, $default) | Get user metadata value |
dv_update_user_meta($user_id, $key, $value) | Set or update user metadata |
Security guidelines
Hardening your DraftVertex CMS installation
Always review and apply these guidelines before deploying to a production server.
File permissions
| Path | Permission | Reason |
|---|---|---|
dv-config.php | 600 or 640 | Contains database credentials and salts |
dv-content/ | 755 dirs / 644 files | Web-accessible user content |
.htaccess | 644 | Web server must read it |
Built-in .htaccess protections
| Block dv-includes/ | Returns 403 Forbidden on direct access |
| Block dv-setup/ | Returns 403 Forbidden on direct access |
| Block dotfiles | Hidden files (.env, .git, etc.) blocked |
| Block sensitive extensions | .sql, .log, .env files return 403 |
| Disable directory listing | Options -Indexes applied globally |
| Hide PHP version | Header X-Powered-By suppressed |
| Block TRACE / TRACK | Dangerous HTTP methods rejected |
| Block vendor/node_modules | Development directories blocked |
Production checklist
| Always use HTTPS | Use a valid TLS certificate in production |
Set DV_DEBUG = false | Never expose error details publicly |
Remove dv-setup/ | After installation is complete |
| Unique salts and keys | Auto-generated during wizard installation |
| Regular backups | Database + dv-content/uploads/ |
| Strong admin password | Use a password manager |
.htaccess reference
Auto-generated by
dv_generate_htaccess() — configured via Preferences → PermalinksMarker system
# BEGIN DV CMS ← Start of managed block (auto-generated) <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / ... </IfModule> # END DV CMS ← End of managed block # Developer custom rules go OUTSIDE the markers # They are preserved on every regeneration
Generated rewrite rules
| Purpose | Pattern → Target |
|---|---|
| Admin assets | ^dv-admin/assets/(.*) → src/dv-admin/assets/$1 |
| Content files | ^dv-content/(.*) → src/dv-content/$1 |
| Admin PHP pages | ^dv-admin/([a-z0-9_-]+).php → src/dv-admin/$1.php |
| Admin clean URLs | ^dv-admin/([a-z0-9_-]+)/?$ → src/dv-admin/$1.php |
| Admin index | ^dv-admin/?$ → src/dv-admin/index.php |
| REST API | ^dv-api/(.*) → src/dv-includes/rest-api.php?_dv_route=$1 |
| XML sitemap | ^sitemap.xml$ → src/dv-includes/sitemaps.php |
| Front-end router | ^(.*)$ → index.php [L,QSA] (if not a real file/dir) |
Extending via filters
// Add rules inside the <IfModule> block dv_add_filter('dv_htaccess_managed_content', function($rules) { return $rules . "\n RewriteRule ^old-url$ /new-url [R=301,L]\n"; }); // Add rules outside the managed block dv_add_filter('dv_htaccess_content', function($content) { return $content . "\n# Custom security header rules\nHeader set X-Frame-Options DENY\n"; });
Site health
Diagnostic checks —
dv-includes/site-health-engine.php — accessible via Admin → ToolsBuilt-in checks
| Check | Requirement |
|---|---|
| PHP version | 8.1 or higher |
| Database connection | Active and reachable |
| PDO extensions | pdo_mysql + pdo_sqlite drivers loaded |
| mbstring extension | Required for Unicode string handling |
| File permissions | config, content/, uploads/ writable |
| .htaccess writability | Required for permalink updates |
| Session configuration | Secure cookie settings active |
| Disk space | Available storage on uploads partition |
Extensions can register their own health checks using the
dv_get_site_health action hook — append to the $results array passed as the first argument.Admin pages reference
All dashboard pages with their URLs and required capabilities
| Page | URL | Capability | Description |
|---|---|---|---|
| Dashboard | /dv-admin/index.php | — | Stats overview and quick actions |
| Posts | /dv-admin/posts.php | edit_posts | Create and manage posts and pages |
| Media | /dv-admin/media.php | upload_files | Media library with grid and list views |
| Categories | /dv-admin/categories.php | manage_categories | Manage taxonomy terms |
| Comments | /dv-admin/comments.php | moderate_comments | Approve, edit, delete user comments |
| Users | /dv-admin/users.php | manage_users | Manage user accounts and roles |
| Profile | /dv-admin/profile.php | — | Edit own profile and password |
| Menus | /dv-admin/menus.php | manage_menus | Build and assign navigation menus |
| Widgets | /dv-admin/components.php | manage_widgets | Manage sidebar widget assignments |
| Customizer | /dv-admin/visuals.php | manage_options | Live preview visual theme settings |
| Preferences | /dv-admin/preferences.php | manage_options | General, permalink, privacy settings |
| Extensions | /dv-admin/extensions.php | manage_extensions | Install, activate, manage extensions |
| Layouts | /dv-admin/layouts.php | manage_layouts | Install and switch layouts |
| Tools | /dv-admin/tools.php | manage_options | Site health, import/export tools |
| Upgrade | /dv-admin/upgrade.php | manage_options | CMS upgrade manager |