⌘K

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:

FileFunction prefixRole
core/data-layer.phpsl_Read-only — index, items, categories, tags, cache
core/admin-data-layer.phpsl_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)

FunctionPurpose
sl_load_index(string $type): arrayLightweight index for 'article', 'page', or 'project'
sl_load_item(string $type, string $fileSlug): ?arrayFull item by its _file slug
sl_load_item_by_slug(string $type, string $slug): ?arrayFull item by public slug (custom_slug ?: slug) — index lookup, no raw-slug fallback
sl_load_all_items(string $type): arrayEvery full item for a type. Expensive — only for search, SEO overview, sitemap
sl_item_path(string $type, string $fileSlug): stringAbsolute path to an item's JSON file; applies basename() first
sl_load_categories(): array / sl_load_tags(): arrayTaxonomy 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): arrayAssembles the $data array consumed by index.php
sl_effective_slug(array $item): stringcustom_slug ?: slug
sl_promote_scheduled(string $type): voidFlips 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;