ThemeCatalog
in package
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
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
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
$skinCatalog read-only
private
SkinCatalog
$skinCatalog
Methods
__construct()
public
__construct(SkinCatalog $skinCatalog) : mixed
Parameters
- $skinCatalog : SkinCatalog
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
stringreadManifest()
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