Data Layer
SynaptikCMS uses a split-file architecture: each content item is its own JSON file, with a lightweight per-type index for fast list pages that never need to read every item.
Two files, two responsibilities:
| File | Function prefix | Role |
|---|---|---|
core/data-layer.php | sl_ | Read-only — index, items, categories, tags, cache |
core/admin-data-layer.php | sl_admin_ | Write — save, delete, trash, restore, revisions, index rebuild |
Index vs. full item
Lightweight index (_index.json), loaded on every request: _file, slug, custom_slug, title, date, category, tags, image, show_in_menu, menu_order, plus per-type extras (articles → summary, show_on_homepage; projects → description, show_on_homepage; pages → page_template).
Full item file ({slug}.json), loaded only for single-item views: everything else — content, SEO fields, galleries, show_date, show_title, og_image, custom fields, author, revisions pointer.
$GLOBALS['data'][$type] is volatile: the index array on list pages, a single-item array on single-item pages. Never assume it holds the full catalogue — call sl_load_index($type) instead.
Caching
Every read function is cached per-request in $GLOBALS['_sl_cache'], and across requests via APCu when the extension is loaded, falling back to a compiled file cache (cache/sl_idx_{type}.cache.php, cache/sl_categories.cache.php, cache/sl_tags.cache.php) otherwise. The write layer invalidates both on every operation.
Read functions (sl_*, core/data-layer.php)
| Function | Purpose |
|---|---|
sl_load_index(string $type): array | Lightweight index for 'article', 'page', or 'project' |
sl_load_item(string $type, string $fileSlug): ?array | Full item by its _file slug |
sl_load_item_by_slug(string $type, string $slug): ?array | Full item by public slug (custom_slug ?: slug) — index lookup, no raw-slug fallback |
sl_load_all_items(string $type): array | Every full item for a type. Expensive — only for search, SEO overview, sitemap |
sl_item_path(string $type, string $fileSlug): string | Absolute path to an item's JSON file; applies basename() first |
sl_load_categories(): array / sl_load_tags(): array | Taxonomy stores |
sl_find_in_index(string $type, string $slug): ?array | [entry, position] from an index, or null |
sl_build_data_array(array $types, bool $fullItems = false): array | Assembles the $data array consumed by index.php |
sl_effective_slug(array $item): string | custom_slug ?: slug |
sl_promote_scheduled(string $type): void | Flips scheduled items to published once their publish date has passed |
Write functions (sl_admin_*, core/admin-data-layer.php)
Items & index
sl_admin_save_item(), sl_admin_delete_item(), sl_admin_write_index(), sl_admin_update_index(), sl_admin_remove_from_index(), sl_admin_save_categories(), sl_admin_save_tags(). All writes are atomic: write to a .tmp file, then rename().
Trash
Deleted items move to data/{type}/.trash/ instead of being removed outright:
sl_admin_trash_item(), sl_admin_restore_trashed_item(), sl_admin_purge_trashed_item(), sl_admin_purge_all_trash(), sl_admin_purge_expired_trash(int $maxAgeDays = 30) (auto-run on a schedule; default 30-day retention), sl_admin_load_trash_index(string $type).
Revisions
Every save snapshots the previous version to data/{type}/.revisions/{fileSlug}/{timestamp}.json:
sl_admin_snapshot_revision(), sl_admin_list_revisions(), sl_admin_load_revision(), sl_admin_restore_revision(), sl_admin_delete_revision(), sl_admin_delete_all_revisions(), sl_admin_migrate_revisions() (reattaches history when a slug changes).
Activity log
sl_admin_log_activity(string $action, string $details = '') appends to private/activity-log.json, read back by sl_admin_load_activity_log(). Used for logins, template edits, extension installs, user changes, and restores — surfaced on the Activity Log page.
Backup / full export
sl_admin_save_all(array $data) and sl_admin_load_all() handle the full-site JSON export/import used by the Backup tool.
How index.php loads data
// 1 — lightweight index for all three types, always
$data = sl_build_data_array(['article', 'page', 'project'], false);
$GLOBALS['data'] = $data;
// 2 — single-item view: overwrite with the one full item
$data[$type] = [$fullItem];
$GLOBALS['data'] = $data;
