Appearance
Subcontent / Embeddable Blocks
Flow ID: AD-49 Module(s): subcontent (legacy), Cms/Subcontent (modern) Complexity: Low Last Updated: 2026-04-04
Business Overview
Subcontent blocks are slug-keyed, multi-language rich text fragments that can be embedded into any storefront page. They provide a lightweight mechanism for store administrators to manage reusable content snippets -- promotional headers, offer banners, informational sections, legal notices, delivery notices -- without creating full CMS pages. Each block is identified by a unique slug and carries per-language name/content pairs.
Unlike CMS pages, subcontent blocks have no hierarchy, no SEO routing, no banner images, and no page view templates. They are purely data containers fetched by slug in storefront controllers and rendered inline. Approximately 34 blocks exist in a typical production database.
Storefront controllers load subcontent by slug via the subcontent_model::getContent() or getContentFieldArrayBySlugs() methods (with pscache TTL caching). Common consumers include the homepage, product pages, product category pages, vendor pages, and offer pages.
API Reference
REST Endpoints
| Method | Path | Action | Auth |
|---|---|---|---|
GET | /rest/cms/subcontent | List subcontent blocks (paginated, filterable) | JWT |
GET | /rest/cms/subcontent/item | Get single block by filters | JWT |
GET | /rest/cms/subcontent/{id} | Get block by ID | JWT |
POST | /rest/cms/subcontent | Create block | JWT |
POST | /rest/cms/subcontent/{id} | Update block | JWT |
DELETE | /rest/cms/subcontent/{id} | Delete block | JWT |
All REST routes support locale-prefixed variants (/{locale}/rest/cms/subcontent).
Filters (REST)
| Filter | Type | Description |
|---|---|---|
id | Exact | Block ID(s) |
slug | Exact | Block slug(s) |
name.{locale} | Partial | Block name by locale |
Relations
| Relation | Type | Description |
|---|---|---|
translations | One-to-Many | MUI translation rows |
Sorts
Root: id, slug Translation: name.{locale}
Admin Panel Endpoints (Legacy HMVC)
| URL | Method | Action |
|---|---|---|
subcontent/subcontent_admin | index() | List all blocks |
subcontent/subcontent_admin/add | add() | Create block form + handler |
subcontent/subcontent_admin/edit/{id} | edit($id) | Edit block form + handler |
subcontent/subcontent_admin/delete/{id} | delete($id) | Delete block |
Code Flow
Storefront Consumption
Subcontent blocks are consumed inline by storefront controllers. They are not routed to directly -- they are fetched by slug and injected into the template render data.
Storefront controller (e.g., Adv_home, Adv_products, Adv_product_categories)
|
v
$this->load->model('subcontent/subcontent_model')
|
v
$this->pscache->model('subcontent_model', 'getContentFieldArrayBySlugs', [$slugs], $ttl)
| Fetches multiple blocks by slug array
| Returns keyed array: slug => content field value
v
Merged into $this->render['{contextSubContent}']
|
v
View template renders subcontent HTML inlineSingle-block fetch:
subcontent_model::getContent($slug, $lang)
| Joins subcontent + subcontent_mui
| Filters by slug and language
v
Returns single row with id, slug, name, fulltext, langAdmin Create Flow
Admin: subcontent/subcontent_admin/add
|
v
Adv_subcontent_admin::add()
| Validates: slug required (ADVISABLE only), name/fulltext per language
| Generates: slug via createSlug() helper
|
v
subcontent_model::addRecord($data, $dataMui)
| Inserts master: { slug }
| Inserts MUI rows: { subcontent_id, name, fulltext, lang }
|
+-- afterAdd($id) [client hook]
|
v
redirect -> subcontent/subcontent_adminREST Create Flow
POST /rest/cms/subcontent
|
v
Subcontent::store() -> doStore()
| Parses JSON body
v
WriteService::create($data)
| Builds WriteData: { slug }
| Validates: slug required, slug unique
| Parses translations array -> MuiWriteData per language
| Validates: lang required per translation
|
v (transactional)
WriteRepository::insert({ slug })
MuiWriteRepository::insertForEntity($id, $translations)
|
v
Repository::get($id, ['translations'])
| Returns created entity with translations
v
Resource -> JSON response (201)REST Update Flow
POST /rest/cms/subcontent/{id}
|
v
Subcontent::update($id) -> doUpdate($id)
|
v
WriteService::update($id, $data)
| Validates: slug not empty if provided, slug unique (excluding self)
| On slug change: checks for duplicates
|
v (transactional)
WriteRepository::update($id, $data) -- only non-null fields
MuiWriteRepository::replaceForEntity($id, $translations) -- full replace
|
v
Repository::get($id, ['translations'])
v
Resource -> JSON response (200)Domain Layer
File Structure
src/Domains/Cms/Subcontent/
Repository/
Entity.php -- id, slug
MuiEntity.php -- id, subcontent_id, name, fulltext, lang
Repository.php -- table: subcontent
RepositoryConfigurator.php -- translations (ONE_TO_MANY via subcontent_id)
MuiRepository.php -- table: subcontent_mui
WriteRepository.php
MuiWriteRepository.php
Specification/
FilterByTranslation.php
SortByTranslation.php
Service.php -- ReadService: all(), item(), get()
WriteService.php -- create(), update(), delete() with transactional writes
ListRequest.php -- Allowed filters (id, slug, name.{locale}), sorts (id, slug, name.{locale})
WriteData.php -- slug only
MuiWriteData.php -- lang, name, fulltext (content)
Validator.php -- Create: slug required + unique. Update: slug unique if changed. Translations: lang required.REST Layer
src/Rest/Cms/Controllers/Subcontent.php -- HandlesRestfulActions + HandlesWriteActions; full CRUD
src/Rest/Cms/Resources/Subcontent/
Resource.php -- id, slug; includes translations relation
Collection.php -- Collects Resource
MuiResource.php -- id, subcontentId, name, content, lang
MuiCollection.php -- Collects MuiResourceLegacy Layer
ecommercen/subcontent/
controllers/
Adv_subcontent_admin.php -- Admin CRUD: index, add, edit, delete
models/
Adv_subcontent_model.php -- All DB operations: CRUD, content lookup by slug, batch slug fetchData Model
subcontent (Master Table)
| Column | Type | Description |
|---|---|---|
id | int, PK, AI | Block ID |
slug | varchar, unique | Identifier slug (e.g., homepage-banner, delivery-notice) |
subcontent_mui (MUI Table)
| Column | Type | Description |
|---|---|---|
id | int, PK, AI | MUI row ID |
subcontent_id | int, FK | References subcontent.id |
lang | varchar(5) | Language code (e.g., el, en) |
name | varchar | Block label/title (admin display) |
fulltext | text | Rich HTML content body |
Entity Relationships
subcontent
|-- 1:N --> subcontent_mui (translations, FK: subcontent_id)Configuration
Subcontent has minimal configuration. No config files, registry keys, or feature gates control its behavior. The feature is always available.
DI Container Registration
Domain services (src/Domains/Cms/container.php):
php
// Repository, RepositoryConfigurator, MuiRepository (with NullRelationConfigurator),
// Service, WriteRepository, MuiWriteRepository, Validator, WriteServiceREST controller (src/Rest/Cms/container.php):
php
$services->set(Controllers\Subcontent::class)
->arg('$service', new Reference(Subcontent\Service::class))
->arg('$resourceClass', Resources\Subcontent\Resource::class)
->arg('$collectionClass', Resources\Subcontent\Collection::class)
->arg('$listRequestClass', Subcontent\ListRequest::class)
->arg('$writeService', new Reference(Subcontent\WriteService::class));Authorization
Admin Panel
| Role | Constant | Access |
|---|---|---|
| Advisable | AUTH_ROLE_ADVISABLE | Full CRUD + slug editing + delete |
| Admin | AUTH_ROLE_ADMIN | Add/edit (no slug editing, no delete) |
| CMS | AUTH_ROLE_CMS | Add/edit (no slug editing, no delete) |
| Media | AUTH_ROLE_MEDIA | Add/edit (no slug editing, no delete) |
Key restrictions:
- Slug field: Only
AUTH_ROLE_ADVISABLEcan set or modify the slug in the admin panel. Non-ADVISABLE roles see a read-only slug or it is not shown. - Deletion: Only
AUTH_ROLE_ADVISABLEcan delete subcontent blocks. Other roles receive an error flash message. - Slug validation: On add, slug is required and generated via
createSlug(). On edit, slug is only updated if the user hasAUTH_ROLE_ADVISABLE.
REST API
JWT authentication required. The REST API does not differentiate role access beyond authentication -- all authenticated users can perform CRUD operations.
Client Extension Points
Admin Controller Hooks
The admin controller provides empty protected methods for client-specific logic:
| Hook | When Called | Signature |
|---|---|---|
afterAdd($id) | After insert | (int $id): void |
afterEdit($id) | After update | (int $id): void |
afterDelete($id) | After delete | (int $id): void |
Domain Layer Override
Client repos can override the repository or validator via DI alias:
php
$services->alias(
\Advisable\Domains\Cms\Subcontent\Repository\RepositoryConfigurator::class,
\Custom\Domains\Cms\Subcontent\Repository\RepositoryConfigurator::class
);Storefront Usage Pattern
Clients embed subcontent by loading the model and fetching by slug. The common patterns are:
Single block:
php
$this->load->model('subcontent/subcontent_model');
$content = $this->subcontent_model->getContent('my-block-slug');
// $content->fulltext contains the HTMLBatch fetch (multiple slugs at once):
php
$slugs = ['banner-top', 'promo-header', 'delivery-notice'];
$data = $this->pscache->model('subcontent_model', 'getContentFieldArrayBySlugs', ['fulltext', $slugs], $ttl);
// Returns: ['banner-top' => '<html>...', 'promo-header' => '<html>...', ...]Business Rules
- Slug uniqueness: Each subcontent block must have a globally unique slug. The REST Validator checks for duplicates on both create and update, throwing a
ValidationExceptionif a collision is found. - Slug immutability (admin): In the admin panel, only ADVISABLE role can modify slugs. This protects storefront code that references blocks by hardcoded slug strings.
- Deletion restriction (admin): Only ADVISABLE role can delete blocks. The
canDeleteRecord()method in the legacy model enforcesisAdvisableUser(). - No SEO routing: Subcontent blocks are not routed to on the storefront. They have no URL, no metatags, and no page view templates.
- No hierarchy: Subcontent blocks are flat -- no parent-child relationships. Each block is a standalone content fragment.
- Language fallback: The
getContent()method fetches by slug + language. If no translation exists for the requested language,nullis returned. There is no automatic fallback to a default language. - Cache layer: Storefront controllers use
pscache(page-scope cache) with a TTL when fetching subcontent. Changes made via admin are visible after cache expiry. - Transactional writes (REST): Both create and update operations are wrapped in database transactions. MUI records are replaced atomically on update (delete all + re-insert).
Storefront Consumers
The following controllers load subcontent blocks for inline rendering:
| Controller | Subcontent Slugs | Context |
|---|---|---|
Adv_home | Various (homepage sections) | Homepage promotional content |
Adv_products | Product page subcontent slugs | Product detail page extras |
Adv_product_categories | Contest + category subcontent | Category listing page sections |
Adv_vendors | Vendor page subcontent | Vendor/brand page sections |
Adv_offers | Offer subcontent | Offer page promotional blocks |
Slug values are typically client-specific and hardcoded in client controller overrides or loaded from registry configuration.
Related Flows
- AD-48 CMS Static Pages -- Full CMS pages; subcontent provides lighter embeddable alternatives
- AD-12 Blog Management -- Blog articles (another CMS content type)
- AD-29 Page Builder -- Visual block builder; subcontent is the simpler text-only alternative
- AD-13 Settings -- Registry settings that may reference subcontent slugs
- SY-23 MUI Translation Pattern — subcontent uses the standard _mui companion table pattern