Appearance
Blog
Flow ID: CF-20 | Module(s): blog, Cms domain | Complexity: Medium
Business Overview
Full blog system with articles, categories, tags, authors, and moderated comments. Articles link to products bi-directionally. Blog has its own _remap() dispatcher that resolves slugs to categories, tags, or articles.
What customers experience:
/blogor/blog/allmain listing with the first article featured/blog/{article-slug}full article page with related articles, products, comments, author bio/blog/{category-slug}category-filtered article listing/blog/{tag-slug}tag-filtered article listing/blog/{author-slug}author page with their articles (6 per page)/blog/search/{term}search results (Solr-powered, supports live AJAX search)/blog/rssRSS feed of all published articles
Key business behaviors:
- Publishing:
is_published = trueANDblog_date <= now()(Future-dated articles are hidden from all users. Only unpublished (draft) articles are visible to logged-in customers.) - Category/author/tag deletion blocked if articles exist
- Optional comments (moderated: pending -> approved/rejected) gated by
OTHER.ENABLE_BLOG_COMMENTSregistry - Blog-product two-way sync configurable (
BLOG_PRODUCT_TWO_WAY_SYNC) - Builder block integration for rich article content via
blogArticleposition - Content embeddings support: product and product list embeds in article body
- Hit counter incremented per article view
- RSS feed with configurable title/description per language via Registry
API Reference
REST Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /rest/cms/blog/article | Guest | List articles (filterable, sortable, paginated) |
| GET | /rest/cms/blog/article/{id} | Guest | Get article by ID |
| POST | /rest/cms/blog/article | Backend (CMS) | Create article (multipart -- banner) |
| POST | /rest/cms/blog/article/{id} | Backend (CMS) | Update article |
| DELETE | /rest/cms/blog/article/{id} | Backend (CMS) | Delete article |
| GET | /rest/cms/blog/author | Guest | List authors |
| POST | /rest/cms/blog/author | Backend (CMS) | Create/update author |
| DELETE | /rest/cms/blog/author/{id} | Backend (CMS) | Delete author |
| GET | /rest/cms/blog/category | Guest | List categories |
| POST | /rest/cms/blog/category | Backend (CMS) | Create/update category |
| DELETE | /rest/cms/blog/category/{id} | Backend (CMS) | Delete category |
| GET | /rest/cms/blog/tag | Guest | List tags |
| POST | /rest/cms/blog/tag | Backend (CMS) | Create/update tag |
| DELETE | /rest/cms/blog/tag/{id} | Backend (CMS) | Delete tag |
| GET | /rest/cms/blog/comment | Guest | List comments |
| POST | /rest/cms/blog/comment | Backend (CMS) | Create/update comment |
| DELETE | /rest/cms/blog/comment/{id} | Backend (CMS) | Delete comment |
Full CRUD for all blog entities with Backend (CMS) auth.
Legacy Storefront
| URL | Method | Description |
|---|---|---|
/blog or /blog/all | index() | Article listing (9/page, first article featured) |
/blog/{article-slug} | blogArticle($slug) | Single article with related content |
/blog/{category-slug} | blogCategory($slug) | Category listing |
/blog/{tag-slug} | blogTag($slug) | Tag listing |
/blog/{author-slug} | blogAuthor($slug) | Author page (6 articles) |
/blog/search/{term}/{page} | search() | Full-page search results |
/blog/search/{term} (AJAX) | search() -> liveSearch() | Live autocomplete search |
/blog/rss | rss() | RSS XML feed |
Code Flow
Step 1: URL Routing via _remap()
File: ecommercen/blog/controllers/Adv_blog.php
all: Routes toindex()- Method exists: Direct call (e.g.,
rss,search) - Category slug match:
blog_category_model->getCategoryBySlug()(cached) ->blogCategory() - Tag slug match:
blog_tags_model->getTagBySlug()(cached) ->blogTag() - Article slug match:
blog_model->getArticleBySlug()(cached) ->blogArticle() - Fallback: 404
Step 2: Article Index
File: ecommercen/blog/controllers/Adv_blog.php::index()
- Pagination: Page number from URL, offset calculated as
(page - 1) * 9 - Query:
blog_model->getBlogsList()withis_published=true, blog_date<=now() - Featured article: First article on page 1 is shifted to
mainArticle - Sidebar: Categories via
getCategoriesFront(), tags viagetTagsFront() - Hreflang: Language switcher URLs via
lang_uri - Hook:
indexExtras()-- empty by default - Tag rendering:
getBlogListingRelatedTags()for sidebar tags - Render:
blogIndexlayout
Step 3: Single Article Page
File: ecommercen/blog/controllers/Adv_blog.php::blogArticle()
- Fetch:
blog_model->getBlogData($slug, $lang)-- 404 if not found or unpublished (guest) - SEO: Meta tags from article MUI fields
- Hreflang: MUI slug lookup per language
- Content embeddings:
renderBlogContentEmbeddings()-- extracts product/product-list embeds from article description - Products:
renderBlogProducts()-- fetches linked products fromblog_producttable (sorted by priority) - Builder block:
blockBuilder->frontRender()forblogArticleposition usingbuilder_block_id - Related articles:
blogRelativeArticles()for sidebar - Author data:
blog_author_model->getAuthorRecord()plus categories - Previous/Next: Navigation links via
getPreviousNext() - Slider: Optional slider if
slider_idset - Hook:
blogArticleExtras()-- loads author, categories, related articles, prev/next by default - Events:
blogArticleEvents()-- empty hook for client - Comments:
manageBlogComments($blogId) - Hit counter:
updateArticleHits($blogId)
Step 4: Comment Submission and Moderation
File: ecommercen/blog/controllers/Adv_blog.php::manageBlogComments()
- Feature gate:
OTHER.ENABLE_BLOG_COMMENTSregistry setting - POST handling: On form submit, validates
comment,blog_commenter_email,blog_commenter_name - Save:
blog_comments_model->saveComment()-- status defaults topending - Flash: Success message, redirect to same page
- Display:
getBlogComments($blogId, 10)-- only showsapprovedcomments, ordered byentry_date DESC
Admin moderation (ecommercen/blog/controllers/Adv_blog_comments_admin.php):
- List all comments with status filter (approved/rejected/pending), search by content/email/date
- Single and batch status updates via
setReviewStatus()/batchUpdateStatus() - Pending count badge via
getBlogCommentsCount()(shows "99+" for large queues)
Step 5: Blog Search
File: ecommercen/blog/controllers/Adv_blog.php::search()
- Guard: Empty search term returns 410 Gone
- AJAX path:
liveSearch($term)-- returns JSON results for autocomplete - Page path:
searchPage($term, $pageNumber)-- full rendered search results page - Solr integration: Routes to
search_model(v1) orsearch_model_v2(v2) based onsolrSearch.versionconfig - Results: Paginated articles matching search term, with sidebar categories/tags
Step 6: RSS Feed
File: ecommercen/blog/controllers/Adv_blog.php::rss()
- Config: Title and description from Registry
BLOG.RSSTITLE/BLOG.RSSDESCRIPTION(per language) - Articles: All published articles via
getBlogsList() - Output: XML content type, rendered via
blog/rssview template
Domain Layer
| Component | Path |
|---|---|
| Article Service | src/Domains/Cms/Blog/Article/Service.php |
| Article WriteService | src/Domains/Cms/Blog/Article/WriteService.php |
| Article Entity | src/Domains/Cms/Blog/Article/Repository/Entity.php |
| REST Controllers | src/Rest/Cms/Controllers/BlogArticle.php, BlogCategory.php, BlogTag.php, BlogAuthor.php, BlogComment.php |
| Legacy Controller | ecommercen/blog/controllers/Adv_blog.php |
| Blog Model | ecommercen/blog/models/Adv_blog_model.php (not shown -- main article queries) |
| Comments Model | ecommercen/blog/models/Adv_blog_comments_model.php |
| Product Link Model | ecommercen/blog/models/Adv_blog_product_model.php |
| Author Model | ecommercen/blog/models/Adv_blog_author_model.php |
| Category Model | ecommercen/blog/models/Adv_blog_category_model.php |
| Tags Model | ecommercen/blog/models/Adv_blog_tags_model.php |
| Admin Controllers | Adv_blog_admin.php, Adv_blog_comments_admin.php, Adv_blog_tags_admin.php |
Data Model
blog
| Column | Type | Description |
|---|---|---|
id | int (PK, AI) | Article ID |
blog_author_id | int (FK) | Author reference |
banner_image | varchar(250) | Banner image path |
blog_date | date | Publish date (future = scheduled) |
is_published | tinyint(1) | Whether article is published |
is_promo | tinyint(1) | Promotional flag |
youtube_video_id | varchar(250) | Embedded YouTube video |
spotify_podcast_id | varchar(250) | Embedded Spotify podcast |
hits | int | View counter |
blog_mui
| Column | Type | Description |
|---|---|---|
id | int (PK, AI) | MUI entry ID |
blog_id | int (FK) | Parent article |
slug | varchar(250) | URL slug |
title | varchar(255) | Article title |
meta_keywords | text | SEO keywords |
meta_description | text | SEO description |
meta_title | text | SEO title |
description | longtext | Full article content (supports content embeddings) |
small_description | text | Excerpt/summary |
lang | varchar(2) | Language code |
blog_comments
| Column | Type | Description |
|---|---|---|
id | int (PK, AI) | Comment ID |
blog_id | int (FK) | Parent article |
email | varchar(255) | Commenter email |
name | varchar(255) | Commenter name |
comment_content | text | Comment text |
entry_date | datetime | Submission timestamp (defaults to current_timestamp()) |
parent_comment_id | int (nullable) | For threaded replies |
status | enum | approved, pending, rejected (default: pending) |
lang | varchar(4) | Language code |
blog_product
| Column | Type | Description |
|---|---|---|
blog_id | int (PK, FK) | Article reference |
product_id | int (PK, FK) | Product reference |
priority | int | Display order (0 = default) |
Supporting Tables
| Table | Purpose |
|---|---|
blog_categories | Category master (banner_image, is_active, ord, parent_id) |
blog_categories_mui | Category translations (title, slug, description, meta fields) |
blog_blog_categories | Article-to-category lookup (blog_id, blog_category_id) |
blog_tags | Tag master (is_active, ord) |
blog_tags_mui | Tag translations (name, slug, description, meta fields) |
blog_blog_tags | Article-to-tag lookup (blog_id, blog_tag_id, unique) |
blog_author | Author master (banner_image, ord) |
blog_author_mui | Author translations (name, slug, description, meta fields) |
Client Extension Points
indexExtras(),blogArticleExtras(),blogCategoryExtras()hooks: Add custom data to respective pagesblogArticleEvents()hook: Custom analytics/tracking events on article view- Comment moderation: Extend admin workflow;
manageBlogComments()is overridable - Product relations:
blog_producttable with priority ordering - RSS feed: Available via
rss()method; customize title/description via Registry - Search: Solr v1/v2 integration via
search_modelorsearch_model_v2 - Content embeddings: Product/product-list shortcodes in article body
- Builder blocks:
blogArticleposition for page builder content - Views:
{client_views}/blog/-- index, article, category, tag, author, search, rss templates
Related Flows
- CF-02 Product Detail -- articles shown on PDP (blog-product two-way sync)
- CF-04 Search -- blog articles appear in search results (Solr v2)
- CF-35 Page Builder --
blogArticlebuilder block position replaces article content - AD-12 Blog Management -- admin CRUD for articles, categories, tags, authors, comments