CoolCMS API

ThemeCatalog
in package

FinalYes

Catalog of front themes; the single place that reads `theme.json` and builds the offer "theme and its color variants".

Shared by the visitor appearance switcher (ThemeSwitcher) and the theme selection in the admin, so manifest reading and color deduplication are written once.

IMPORTANT: like SkinCatalog, the service has no dependency on DefaultThemeResolver on purpose: in the admin the resolver holds the admin branch theme and its frontendThemesDir() returns NULL there. The themes directory is therefore passed in as a parameter: front from the resolver, admin from a constant.

Tags
phpstan-type

ThemeVariant array{skin: ?string, label: ?string, color: ?string, duplicateColor: bool}

ThemeItem array{ name: string, label: string, description: ?string, color: ?string, group: string, groupOrder: int, groupLabelKey: string, variants: list<ThemeVariant> }

Table of Contents

Constants

GROUPS  : array<string, array{order: int, labelKey: string}> = [ // Group of the admin branch. A front theme n...
Groups the themes are sorted into: slug to section order and translation key.
FALLBACK_GROUP  : mixed = 'other'
Group for a theme without `group` and for a slug missing from the dictionary.
MANIFEST_FILE  : mixed = 'theme.json'
Optional descriptive file in the theme directory (`www/themes/frontend/<dir>/theme.json`) with keys `label`, `description`, `color` and `group` (for example `label` "Real estate", `color` "#2ecc8a", `group` "classifieds").

Properties

$skinCatalog  : SkinCatalog

Methods

__construct()  : mixed
groupedThemes()  : array<int, ThemeItem>}>
Same, grouped into sections for a screen that lists themes under headings.
listThemes()  : array<int, ThemeItem>
Returns the themes in the directory, sorted naturally by directory name (`template2` before `template10`).
variants()  : array<int, ThemeVariant>
Returns the color variants of one theme: the base appearance followed by its skins.
colorKey()  : string
Returns the key for comparing two colors from manifests.
readManifest()  : array{label?: string, description?: string, color?: string, group?: string}
Reads `theme.json`. A broken or missing file is skipped silently: a typo in someone else's manifest must not break the offer.
scanThemeNames()  : array<int, string>
Returns the theme directory names. Only a directory with `theme.json` counts as a theme, so a stray folder or `.DS_Store` does not get into the offer.

Constants

GROUPS

Groups the themes are sorted into: slug to section order and translation key.

public array<string, array{order: int, labelKey: string}> GROUPS = [ // Group of the admin branch. A front theme never uses it (and if a manifest named it, it // would show as an empty section in the appearance settings), but the admin directory has // one theme and without its own slug its fieldset legend would say "Universal". 'admin' => ['order' => 5, 'labelKey' => 'groups.admin'], 'eshop' => ['order' => 10, 'labelKey' => 'groups.eshop'], 'classifieds' => ['order' => 20, 'labelKey' => 'groups.classifieds'], 'company' => ['order' => 30, 'labelKey' => 'groups.company'], 'magazine' => ['order' => 40, 'labelKey' => 'groups.magazine'], 'other' => ['order' => 90, 'labelKey' => 'groups.other'], ]

IMPORTANT: the theme manifest only says "I belong to eshop"; the CMS owns the group name. Free text from a manifest would never reach the translations (cs/sk/en), and a missing translation key takes down the whole page. Same reasoning as for label/description, which are deliberately not translation keys.

labelKey is relative to the domain of the screen that lists the groups (admin.system.presenters.themeSetting); the service itself does not translate.

A new group means one row here plus three translations. An unknown or missing slug falls into self::FALLBACK_GROUP, so a typo in someone else's manifest never crashes the page.

FALLBACK_GROUP

Group for a theme without `group` and for a slug missing from the dictionary.

private mixed FALLBACK_GROUP = 'other'

MANIFEST_FILE

Optional descriptive file in the theme directory (`www/themes/frontend/<dir>/theme.json`) with keys `label`, `description`, `color` and `group` (for example `label` "Real estate", `color` "#2ecc8a", `group` "classifieds").

private mixed MANIFEST_FILE = 'theme.json'

Without it the front switcher (which passes $onlyNames) offers the theme under its directory name, but the admin listing (scanThemeNames()) and bin/theme-previews.php skip the directory entirely.

The texts are deliberately not translation keys: a missing translation takes down the whole page, while a missing manifest key only loses a nice name. They are also marketing texts of a concrete theme, not of the CMS, so they belong in its directory, not in app/Locale/.

color is the color of the dot in the offer. When missing the dot is neutral: deriving it from style.css is not cheap, since the file is over half a megabyte and --color-primary lies about 230 kB deep. For skins this is no problem, skin.css is a few kB (SkinCatalog).

group is a slug from the dictionary self::GROUPS, not free text.

NOTE: the manifest also knows the key subpage ("subpage": "/eshop"), which the catalog does not read: it is the address of a subpage for the theme gallery and only the preview generator (bin/theme-previews.php) uses it. Do not delete it as an unknown key.

Properties

Methods

groupedThemes()

Same, grouped into sections for a screen that lists themes under headings.

public groupedThemes(string $absThemesDir[, array<int, string>|null $onlyNames = null ]) : array<int, ThemeItem>}>

Groups without a single theme are omitted: an empty heading would only claim that something is missing.

Parameters
$absThemesDir : string
$onlyNames : array<int, string>|null = null
Return values
array<int, ThemeItem>}>

listThemes()

Returns the themes in the directory, sorted naturally by directory name (`template2` before `template10`).

public listThemes(string $absThemesDir[, array<int, string>|null $onlyNames = null ]) : array<int, ThemeItem>

$onlyNames limits the selection to the listed themes: the front passes the list from the resolver (respecting a disabled switch), the admin passes NULL and gets every theme in the directory.

Parameters
$absThemesDir : string

absolute path to the themes directory, with a trailing slash

$onlyNames : array<int, string>|null = null
Return values
array<int, ThemeItem>

variants()

Returns the color variants of one theme: the base appearance followed by its skins.

public variants(string $absThemePath, string|null $themeColor) : array<int, ThemeVariant>

NOTE: skins are read from the directory of every offered theme, not only the displayed one, so a visitor sees at once which colors each theme has.

duplicateColor marks a variant whose color already appears in the list. Themes often have a skin that merely names their own base color, or two copies of one skin under different names; two dots of the same color side by side look like a bug and clicking shows no difference. The first occurrence wins: base appearance before a skin, lower order before higher.

IMPORTANT: the duplicate is only marked here, not dropped. The visitor switcher has an exception the catalog does not know: the skin currently on must stay in the offer even with a duplicate color, otherwise the check mark would have nothing to render on and the panel would claim nothing is selected. The caller decides; the catalog only knows what a repeated color is.

Parameters
$absThemePath : string
$themeColor : string|null
Return values
array<int, ThemeVariant>

colorKey()

Returns the key for comparing two colors from manifests.

private colorKey(string $color) : string

Manifests are written by people, so #FFF, #ffffff and #FFFFFF are still the same white. The short form is expanded to six digits, the rest (rgb(...), a color name) only loses spaces and capitals; that is enough for an exact match, and the comparison only decides whether a dot repeated.

Parameters
$color : string
Return values
string

readManifest()

Reads `theme.json`. A broken or missing file is skipped silently: a typo in someone else's manifest must not break the offer.

private readManifest(string $path) : array{label?: string, description?: string, color?: string, group?: string}
Parameters
$path : string
Return values
array{label?: string, description?: string, color?: string, group?: string}

scanThemeNames()

Returns the theme directory names. Only a directory with `theme.json` counts as a theme, so a stray folder or `.DS_Store` does not get into the offer.

private scanThemeNames(string $dir) : array<int, string>
Parameters
$dir : string
Return values
array<int, string>
On this page

Search results