Appearance
Blog Management (Admin)
Flow ID: AD-12 Module(s): blog Complexity: Medium Last Updated: 2026-04-04
Business Context
The blog module provides a full CMS for article publishing with hierarchical categories, author profiles, color-coded tags, optional comment moderation, product linking, builder block integration, and AI content generation. All blog entities use MUI (multi-language) companion tables. The legacy HMVC layer (ecommercen/blog/) handles the admin UI, and a modern domain/REST layer also exists in src/: src/Domains/Cms/Blog/ (Article, Author, Category, Comment, and Tag entities) and src/Rest/Cms/Controllers/ (BlogArticle, BlogAuthor, BlogCategory, BlogComment, BlogTag controllers) with full REST CRUD.
Authorization
All blog admin controllers require one of three roles:
| Role | Constant | Articles | Categories | Tags | Authors | Comments |
|---|---|---|---|---|---|---|
| Advisable | AUTH_ROLE_ADVISABLE | Full + slug edit | Full | Full | Full | Full |
| Admin | AUTH_ROLE_ADMIN | Full | Full | Full | Full | Full |
| CMS | AUTH_ROLE_CMS | Full | Full | Full | Full | -- |
| Marketing | AUTH_ROLE_MARKETING | -- | -- | -- | -- | Full |
Comments controller (Adv_blog_comments_admin) uses AUTH_ROLE_MARKETING instead of AUTH_ROLE_CMS.
Role-gated field access:
- Metatags (
meta_title,meta_keywords,meta_description): RequirescanAccess('metatags', $sessionRoles) - URL fields (
slug,url,redirect_url): RequirescanAccess('urlFields', $sessionRoles)-- slug editing on existing records restricted to ADVISABLE role
Article Management
Controller: ecommercen/blog/controllers/Adv_blog_admin.php (509 lines) Model: ecommercen/blog/models/Adv_blog_model.phpTables: blog (master), blog_mui (MUI), blog_blog_categories (LP), blog_blog_tags (LP)
Key Methods
| Method | Action |
|---|---|
index($offset) | Paginated listing with session-persisted search filters (published, promo, author, keyword) |
resetIndex() | Clear search filters and redirect |
add() | Create article with MUI content, category/tag assignments, image upload, blog-product relations |
edit($id) | Update article; loads record as master+details, re-renders category/tag/product combos |
delete($id) | Delete article, its MUI records, category/tag link-pairs, and all associated comments |
saveBlogProducts($blogId) | Save blog-product associations with optional two-way sync |
Article Data Fields
Master table (blog):
slider_id(nullable FK to sliders)blog_author_id(nullable FK toblog_author)youtube_video_id(extracted viaextractYouTubeId())spotify_podcast_idis_published(0/1)is_promo(0/1)blog_date(defaults to current date)reading_timebanner_image(uploaded tofiles/blogs/)hits(view counter, incremented on frontend)
MUI table (blog_mui) per language:
title(required)description(rich text, unescaped viastripslashes)small_description(rich text excerpt)slug(auto-generated from title on create; editable only by ADVISABLE on update)builder_block_id(nullable FK for builder block integration)meta_title,meta_keywords,meta_description(metatags-gated)url,redirect_url(urlFields-gated)
Slug Generation Logic
- On create: Always auto-generated from title via
createSlug() - On update: If ADVISABLE role, slug is taken from the POST field (can be manually edited). If slug is empty and no existing slug exists, auto-generated from title. Non-ADVISABLE roles cannot modify the slug.
Search / Filtering
Session-stored filters (artSearch session key):
published:'true'/'false'/null(all)promo:'true'/'false'/ emptysearchAll: Matches againstblog.id,blog_mui.slug, orblog_mui.title LIKEauthor: Author ID filter (where-in onblog_author.id)artLimit: Persisted per-page limit
Relations Management
- Categories: Multi-select via
categoryIds[]POST. On create:insertCategoriesLp(). On update: delete-and-reinsert viaupdateCategoriesLp(). Category is required (validated). - Tags: Multi-select via
tagIds[]POST. Same delete-and-reinsert pattern viaupdateTagsLp(). - Products: Managed via
saveBlogProducts()on article save (see Blog-Product Linking below).
Delete Cascade
delete($id) removes:
blogmaster recordblog_muiMUI recordsblog_blog_categorieslink-pair recordsblog_blog_tagslink-pair recordsblog_commentsrecords (viablog_comments_model->deleteBlogComments())
Extensibility Hooks
The controller provides empty protected methods for client overrides:
beforeAddRecord($data, $dataMui)/beforeEditRecord($id, $data, $dataMui)-- pre-save data mutationafterAdd($id)/afterEdit($id)/afterDelete($id)-- post-save side effectsblogRender()/blogAddRender()/blogEditRender()-- inject extra render data
AI Content Generation
The article edit view integrates aiContentGenerationTrait (from ecommercen/ai/traits/), which:
- Loads prompt config from
ai_content_generation_promptsconfig file - Merges registry overrides from
AI_CONTENT_GENERATION_PROMPTSgroup - Passes JSON state to the admin view with
provider,enabledflag,prompts,storeUrl, andstoreName
Enabled only when registry key IS_ENABLED in group AI_CONTENT_GENERATION_PROVIDER_OPEN_AI is truthy.
Category Management
Controller: ecommercen/blog/controllers/Adv_blog_category_admin.php (390 lines) Model: ecommercen/blog/models/Adv_blog_category_model.phpTables: blog_categories (master), blog_categories_mui (MUI), blog_blog_categories (LP)
Key Methods
| Method | Action |
|---|---|
index($parentId) | Hierarchical listing with breadcrumb navigation; counts articles per category |
add() | Create with parent assignment, 3 images, MUI content |
edit($blogCategoryId) | Update all fields; supports individual image deletion |
delete($blogCategoryId) | Delete only if no articles linked (checked via allowedCategoryDeletion) |
updateorder() | AJAX drag-and-drop reorder via listItem POST array |
Category Data Fields
Master table (blog_categories):
parent_id(0 = root; supports nested hierarchy)is_active(visibility toggle)banner_image,small_banner,promo_banner(3 images, all uploaded tofiles/blogs/)promo_banner_url(link target for promo banner)ord(ordering position)
MUI table (blog_categories_mui) per language:
title(required)descriptionslug(auto-generated on create; editable by ADVISABLE on update)meta_title,meta_keywords,meta_description(metatags-gated)url,redirect_url(urlFields-gated)
Hierarchy
Categories support parent-child nesting with recursive breadcrumb building (buildCategoryAncestry()). The listing view shows subcategory count per category and navigates into children via index($parentId).
Business Rules
- Deletion blocked if any articles are linked to the category (
allowedCategoryDeletionchecksblog_blog_categories) - Ordering: Drag-and-drop reorder updates
ordcolumn via AJAX - Categories are presented as a flat dropdown combo in article forms (via
categoriesCombo())
Author Management
Controller: ecommercen/blog/controllers/Adv_blog_author_admin.php (359 lines) Model: ecommercen/blog/models/Adv_blog_author_model.phpTables: blog_author (master), blog_author_mui (MUI)
Key Methods
| Method | Action |
|---|---|
index() | List all authors with article count per author |
add() | Create with banner image, MUI name/description/small_description |
edit($id) | Update; supports image deletion |
delete($id) | Delete only if no articles assigned (checked via allowedAuthorDeletion) |
Author Data Fields
Master table (blog_author):
banner_image(uploaded tofiles/blogs/)
MUI table (blog_author_mui) per language:
name(required)descriptionsmall_descriptionslug(auto-generated from name on create; editable by ADVISABLE on update)meta_title,meta_keywords,meta_description(metatags-gated)url,redirect_url(urlFields-gated)
Business Rules
- Deletion blocked if any articles reference the author (
allowedAuthorDeletionchecksblogtable forblog_author_id) - Author is optional on articles (nullable
blog_author_id)
Tag Management
Controller: ecommercen/blog/controllers/Adv_blog_tags_admin.php (357 lines) Model: ecommercen/blog/models/Adv_blog_tags_model.phpTables: blog_tags (master), blog_tags_mui (MUI), blog_blog_tags (LP)
Key Methods
| Method | Action |
|---|---|
index() | Sortable list of all tags |
add() | Create with active flag, hex color, MUI title/description |
edit($id) | Update; same fields |
delete($id) | Delete only if no articles linked (checked via allowedTagDeletion) |
updateorder() | AJAX drag-and-drop reorder |
Tag Data Fields
Master table (blog_tags):
is_active(visibility toggle)ord(ordering position)
MUI table (blog_tags_mui) per language:
title(required)descriptionhexcode(hex color code, shared across all languages -- stored in MUI table but same value posted for all)slug(auto-generated from title on create; editable by ADVISABLE on update)meta_title,meta_keywords,meta_description(metatags-gated)url,redirect_url(urlFields-gated)
Business Rules
- Deletion blocked if any articles are tagged with it (
allowedTagDeletionchecksblog_blog_tags) - On successful deletion: success flash message. On blocked deletion: error flash message (
eshop_errorsession key). - Tags displayed as multi-select dropdown combo in article forms
Comment Moderation
Controller: ecommercen/blog/controllers/Adv_blog_comments_admin.php (179 lines) Model: ecommercen/blog/models/Adv_blog_comments_model.phpTable: blog_comments
Feature Gate
The entire comments subsystem is gated by registry setting OTHER.ENABLE_BLOG_COMMENTS. If disabled, the comments admin controller redirects to the admin home page. The article listing also conditionally shows a pending comments badge count.
Key Methods
| Method | Action |
|---|---|
index($offset) | Paginated listing of comments with search/filter |
resetIndex() | Clear search filters |
setStatus($commentId, $status) | Set comment to approved/rejected/pending + send email notification |
batchAction() | Bulk status update for selected comments |
Comment Status Flow
pending --> approved (sends blogCommentAccept email)
pending --> rejected (sends blogCommentReject email)
approved --> rejected (sends blogCommentReject email)
rejected --> approved (sends blogCommentAccept email)Email Notification
On setStatus(), the controller:
- Fetches the comment record (
getCurrentRecord) - Builds a
customerDataobject withmail,name,comment,blog_id,lang - Calls
Adv_mailer::sendBlogCommentStatusUpdateEmail()which sends via theEMAIL_CONTACT_FORMconfig usingEMAIL_SUBJECTS.BLOG_COMMENT_ACCEPTorEMAIL_SUBJECTS.BLOG_COMMENT_REJECTregistry subjects
Search Filters
commentStatus:'pending'(default) /'true'(approved) /'false'(rejected) / allsearchAll: Matches against comment ID, blog title, commenter name, or comment contentsearchMail: Email address filterdate_start: Date range filter
Comment Data Fields (blog_comments table)
blog_id(FK to article)name,email(commenter info)comment_contentstatus(pending/approved/rejected)entry_datelang
Blog-Product Linking
Controller: ecommercen/blog/controllers/AdvApiBlogProduct.php (140 lines) Model: ecommercen/blog/models/Adv_blog_product_model.phpTable: blog_product (blog_id, product_id, priority)
Feature Gate
Gated by registry setting ESHOP.BLOG_PRODUCT_ENABLED. When disabled, the API returns 404 and the article save skips product processing.
API Endpoints (JSON, ApiEndpointTrait)
| Method | HTTP | Action |
|---|---|---|
addProduct() | POST | Add single product to article with priority |
deleteProduct() | DELETE | Remove single product from article |
repositionProducts() | POST | Batch reorder products for an article |
All endpoints validate via form_validation with blogId (required int), productId (required int for add/delete), priority (optional int).
Two-Way Sync
When ESHOP.BLOG_PRODUCT_TWO_WAY_SYNC is enabled, product-article links are maintained in both directions:
blog_producttable: blog-side (managed byAdv_blog_product_model)product_blogtable: product-side (managed byAdv_product_blog_modelinecommercen/eshop/models/)
Both the API controller and the article save method (saveBlogProducts) honor this sync. The product admin also uses product_blog_model for the reverse direction.
Product Display in Article Edit
renderBlogProducts() enriches linked products with live data (stock, codes, images) via helper functions (getLiveProductsParsed, getProductsCodesParsed, getProductsCodeImagesParsed) and sorts by priority.
Database Schema
Tables
| Table | Type | Description |
|---|---|---|
blog | Master | Article records |
blog_mui | MUI | Article multi-language content |
blog_categories | Master | Category hierarchy |
blog_categories_mui | MUI | Category multi-language content |
blog_author | Master | Author profiles |
blog_author_mui | MUI | Author multi-language content |
blog_tags | Master | Tags with ordering |
blog_tags_mui | MUI | Tag multi-language content with hex color |
blog_blog_categories | Link-pair | Article-to-category many-to-many |
blog_blog_tags | Link-pair | Article-to-tag many-to-many |
blog_product | Link-pair | Article-to-product with priority |
product_blog | Link-pair | Product-to-article (reverse sync) |
blog_comments | Flat | User-submitted comments with moderation status |
blog_default | Master | Default blog page settings |
blog_default_mui | MUI | Default blog page MUI content |
Registry Settings
| Group | Key | Effect |
|---|---|---|
ESHOP | BLOG_PRODUCT_ENABLED | Enables blog-product linking feature |
ESHOP | BLOG_PRODUCT_TWO_WAY_SYNC | Enables bidirectional sync between blog_product and product_blog |
ESHOP | BLOG_PRODUCT_CATEGORY_ENABLED | Enables blog-product-category cross-linking |
OTHER | ENABLE_BLOG_COMMENTS | Enables comment submission and moderation |
EMAIL_SUBJECTS | BLOG_COMMENT_ACCEPT | Email subject for comment approval notification |
EMAIL_SUBJECTS | BLOG_COMMENT_REJECT | Email subject for comment rejection notification |
AI_CONTENT_GENERATION_PROVIDER_OPEN_AI | IS_ENABLED | Enables AI content generation in article editor |
File Upload
All blog images are uploaded to files/blogs/ via the advuploader library. Upload methods validate file types and report errors to the view. Image deletion clears the DB field and calls storage()->delete() on the physical file.
Routes
php
// application/config/my_routes.php
$route['smileblog'] = 'blog';
$route['smileblog/(:any)'] = 'blog/$1';Admin routes follow standard HMVC convention: blog/blog_admin, blog/blog_category_admin, blog/blog_author_admin, blog/blog_tags_admin, blog/blog_comments_admin, blog/advapiblogproduct.
Related Flows
- CF-20 Blog Frontend -- Storefront blog display, article views, comment submission
- AD-02 Product Management -- Product-side blog linking (reverse sync)
- AD-05 Category Management -- Blog article associations with product categories (when
BLOG_PRODUCT_CATEGORY_ENABLED) - AD-13 Settings -- Blog feature toggles in admin settings
- AD-21 Slider Management -- Blog articles can embed a slider via
slider_id - AD-29 Page Builder (Admin) -- Builder blocks referenced by
builder_block_idin articles (see Builder guide) - AD-47 AI Content Generation -- AI-assisted content generation for article descriptions
- SY-23 MUI Translation Pattern -- all blog entities (
blog,blog_categories,blog_author,blog_tags) use_muicompanion tables - SY-24 Email Dispatch System --
blogCommentAccept/blogCommentRejectemails dispatched viaAdv_maileron comment status change - SY-25 File Upload & Storage — blog image uploads via Advuploader