davidhirtz / yii2-cms
Content management system based on Yii 2.0 framework
Requires
- php: ^8.3
- davidhirtz/yii2-media: ^3.1.1
- davidhirtz/yii2-tenant: ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.5
- symfony/browser-kit: ^7.4
- symfony/css-selector: ^7.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main / 3.x-dev
- 3.1.0
- 3.0.0
- v2.x-dev
- 2.4.14
- 2.4.13
- 2.4.12
- 2.4.11
- 2.4.10
- 2.4.9
- 2.4.8
- 2.4.7
- 2.4.6
- 2.4.5
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.14
- 2.3.13
- 2.3.12
- 2.3.11
- 2.3.10
- 2.3.9
- 2.3.8
- 2.3.7
- 2.3.6
- 2.3.5
- 2.3.4
- 2.3.3
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.27
- 2.1.26
- 2.1.25
- 2.1.24
- 2.1.23
- 2.1.22
- 2.1.21
- 2.1.20
- 2.1.19
- 2.1.18
- 2.1.17
- 2.1.16
- 2.1.15
- 2.1.14
- 2.1.13
- v2.1.12
- v2.1.11
- v2.1.10
- v2.1.9
- v2.1.7
- v2.1.6
- v2.1.5
- v2.1.4.1
- v2.1.4
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.23
- v2.0.22
- v2.0.21
- v2.0.20
- v2.0.19
- v2.0.18
- v2.0.17
- v2.0.16
- v2.0.15
- v2.0.14
- v2.0.13
- v2.0.12
- v2.0.11
- v2.0.10
- v2.0.9
- v2.0.8
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.x-dev
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.26
- v1.2.25
- v1.2.24
- v1.2.23
- v1.2.22
- v1.2.21
- v1.2.20
- v1.2.19
- v1.2.18
- v1.2.17
- v1.2.16
- v1.2.15
- v1.2.14
- v1.2.13
- v1.2.12
- v1.2.11
- v1.2.10
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- dev-next
- dev-master
This package is auto-updated.
Last update: 2026-09-24 17:37:40 UTC
README
Content management for the yii2-skeleton admin: entries (pages, articles, anything with a URL), optionally nested, put into categories and menus, and built from ordered sections; blocks are sections shared between entries. Entries, sections and blocks carry assets from yii2-media, and every entry belongs to a tenant of yii2-tenant. The bundle ships the admin and a frontend controller; the site's layout and views are the project's.
Installation
composer require davidhirtz/yii2-cms ./yii migrate ./yii search/rebuild
The bundle bootstraps itself through extra.bootstrap (Hirtz\Cms\Bootstrap): it registers the cms module, the
admin/cms submodule, the permalink console command, the @cms alias, the cms message category, the block,
category, entry and section models plus their assets on the search component, the BlockAsset, EntryAsset and
SectionAsset classes on modules.media.assets, and two URL rules: '' → cms/site/index and the catch-all
<slug:.+> → cms/site/view. The catch-all claims every path no other rule matches, so a project's own rules need a
lower position (the cms rules sit at 1000 and 1100). The migration seeds the entry and category permissions and
the author role. Upgrading from 2.x: see UPGRADE.md.
Configuration
modules.cms
| Property | Default | Meaning |
|---|---|---|
categoryCachedQueryDuration |
60 |
seconds the category list is cached; false disables |
defaultEntryOrderBy |
['position' => SORT_ASC] |
entry order when neither the type nor the category sets one |
defaultEntryType |
null |
entry type the admin's default links filter by |
enableBlockAssets |
true |
blocks have assets |
enableBlockEntries |
true |
blocks link entries |
enableBlocks |
false |
sections may carry a shared block |
enableCategories |
false |
entries are put into categories |
enableEntryAssets |
true |
entries have assets |
enableNestedCategories |
true |
categories form a tree |
enableNestedEntries |
false |
entries form a tree |
enableSectionAssets |
true |
sections have assets |
enableSectionEntries |
false |
sections link entries |
enableSections |
true |
entries have sections |
enableUrlRules |
true |
register the two frontend URL rules |
entryIndexSlug |
'home' |
slug of the top-level entry served at /; false serves nothing there |
entryRelations |
BlockEntry, SectionEntry |
Models\EntryRelation subclasses, one per model that links entries |
inheritNestedCategories |
true |
an entry is also in the ancestors of its categories |
menus |
[] |
Models\Menus\Menu definitions, see below |
sectionSets |
[] |
Models\Sets\SectionSet definitions, see below |
The flags cascade in init(): without sections there are no section assets and no blocks, without blocks no block
assets or block entries, without categories no nested categories, and without nested categories nothing is inherited.
A type only ever narrows what the module turned on.
Types
Entries, sections, categories and blocks are typed. Their types are fluent definitions set on the container as a closure,
because a name is usually a Yii::t() result and the configuration is read before the application has an i18n
component:
use Hirtz\Cms\Models\Entry; use Hirtz\Cms\Models\EntryAsset; use Hirtz\Cms\Models\Section; use Hirtz\Cms\Models\Types\BlockSectionType; use Hirtz\Cms\Models\Types\EntryType; use Hirtz\Cms\Models\Types\SectionType; use Hirtz\Skeleton\Models\CustomAttributes\TextCustomAttribute; 'container' => [ 'definitions' => [ Entry::class => [ 'i18nAttributes' => ['name', 'slug'], 'types' => fn (): array => [ EntryType::make(1)->name('Page')->plural('Pages')->allowCategories(false), EntryType::make(2)->name('Article')->plural('Articles') ->orderBy(['publish_date' => SORT_DESC]) ->viewFile('article') ->customAttributes([TextCustomAttribute::make('author')]), ], ], Section::class => [ 'types' => fn (): array => [ SectionType::make(1)->name('Text'), SectionType::make(2)->name('Gallery')->hiddenFields('content'), BlockSectionType::make(3), ], ], EntryAsset::class => ['translatableAttributes' => ['alt_text']], ], ],
Every type takes name(), plural(), viewFile(), cssClass(), customAttributes() and hiddenFields().
Per model:
EntryType:allowCategories(),allowSections(),allowDescendants(),allowAssets();orderBy()sets the index order and turns off manual ordering;sort(),showCategories(),showCategoryDropdown()configure the admin grid.SectionType:allowBlock()(off by default, needsenableBlocks),allowEntries(),entriesTypes(),entriesOrderBy(),allowAssets(); forWidgets\SectionStackon the site,visible(),group(),wrapper()andcollect();gridContent()replaces the name in the admin grid.BlockSectionTypeis a ready-made type whose sections only carry a block.CategoryType:allowDescendants(),allowEntries().BlockType:allowEntries(),entriesTypes(),entriesOrderBy(),allowAssets().
Menus and section sets
Both are declared on the module, as closures for the same reason:
use Hirtz\Cms\Models\Menus\Menu; use Hirtz\Cms\Models\Sets\SectionSet; use Hirtz\Cms\Models\Sets\SectionTemplate; 'modules' => [ 'cms' => [ 'enableCategories' => true, 'menus' => fn (): array => [ Menu::make(1)->name('Main menu'), Menu::make(2)->name('Legal')->available(fn (Entry $entry): bool => $entry->type === 1)->autoload(false), ], 'sectionSets' => fn (): array => [ SectionSet::make(1)->name('Landing page')->sections( SectionTemplate::make(1)->attribute('name', 'Intro'), SectionTemplate::make(2), ), ], ], ],
A menu an entry is put into is stored in entry.menu_ids; Models\Collections\MenuCollection loads every autoload
menu in one query. A section set creates its sections on an entry in one go and must name declared section types.
available() limits either to some entries; a menu an entry is already in stays valid.
Console commands
permalink/rebuild— rewrites every entry's permalinks, inserting the missing ones and deleting those of entries that no longer have a URL (after adding a language, for instance)
On the site
Controllers\SiteController resolves the slug to an entry, answers 404 for one without a URL, and renders the type's
viewFile or view, with the layout main. Widgets\SectionStack::make()->entry($entry) renders the visible
sections, grouped and wrapped as their types say; Widgets\Artwork and Widgets\Gallery render assets, and
Widgets\MetaTags the page's meta tags and hreflang links. Saving any cms record invalidates the page cache
(Module::invalidatePageCache()).