Skip to content

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:

RoleConstantArticlesCategoriesTagsAuthorsComments
AdvisableAUTH_ROLE_ADVISABLEFull + slug editFullFullFullFull
AdminAUTH_ROLE_ADMINFullFullFullFullFull
CMSAUTH_ROLE_CMSFullFullFullFull--
MarketingAUTH_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): Requires canAccess('metatags', $sessionRoles)
  • URL fields (slug, url, redirect_url): Requires canAccess('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

MethodAction
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 to blog_author)
  • youtube_video_id (extracted via extractYouTubeId())
  • spotify_podcast_id
  • is_published (0/1)
  • is_promo (0/1)
  • blog_date (defaults to current date)
  • reading_time
  • banner_image (uploaded to files/blogs/)
  • hits (view counter, incremented on frontend)

MUI table (blog_mui) per language:

  • title (required)
  • description (rich text, unescaped via stripslashes)
  • 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' / empty
  • searchAll: Matches against blog.id, blog_mui.slug, or blog_mui.title LIKE
  • author: Author ID filter (where-in on blog_author.id)
  • artLimit: Persisted per-page limit

Relations Management

  • Categories: Multi-select via categoryIds[] POST. On create: insertCategoriesLp(). On update: delete-and-reinsert via updateCategoriesLp(). Category is required (validated).
  • Tags: Multi-select via tagIds[] POST. Same delete-and-reinsert pattern via updateTagsLp().
  • Products: Managed via saveBlogProducts() on article save (see Blog-Product Linking below).

Delete Cascade

delete($id) removes:

  1. blog master record
  2. blog_mui MUI records
  3. blog_blog_categories link-pair records
  4. blog_blog_tags link-pair records
  5. blog_comments records (via blog_comments_model->deleteBlogComments())

Extensibility Hooks

The controller provides empty protected methods for client overrides:

  • beforeAddRecord($data, $dataMui) / beforeEditRecord($id, $data, $dataMui) -- pre-save data mutation
  • afterAdd($id) / afterEdit($id) / afterDelete($id) -- post-save side effects
  • blogRender() / blogAddRender() / blogEditRender() -- inject extra render data

AI Content Generation

The article edit view integrates aiContentGenerationTrait (from ecommercen/ai/traits/), which:

  1. Loads prompt config from ai_content_generation_prompts config file
  2. Merges registry overrides from AI_CONTENT_GENERATION_PROMPTS group
  3. Passes JSON state to the admin view with provider, enabled flag, prompts, storeUrl, and storeName

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

MethodAction
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 to files/blogs/)
  • promo_banner_url (link target for promo banner)
  • ord (ordering position)

MUI table (blog_categories_mui) per language:

  • title (required)
  • description
  • slug (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 (allowedCategoryDeletion checks blog_blog_categories)
  • Ordering: Drag-and-drop reorder updates ord column 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

MethodAction
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 to files/blogs/)

MUI table (blog_author_mui) per language:

  • name (required)
  • description
  • small_description
  • slug (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 (allowedAuthorDeletion checks blog table for blog_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

MethodAction
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)
  • description
  • hexcode (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 (allowedTagDeletion checks blog_blog_tags)
  • On successful deletion: success flash message. On blocked deletion: error flash message (eshop_error session 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

MethodAction
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:

  1. Fetches the comment record (getCurrentRecord)
  2. Builds a customerData object with mail, name, comment, blog_id, lang
  3. Calls Adv_mailer::sendBlogCommentStatusUpdateEmail() which sends via the EMAIL_CONTACT_FORM config using EMAIL_SUBJECTS.BLOG_COMMENT_ACCEPT or EMAIL_SUBJECTS.BLOG_COMMENT_REJECT registry subjects

Search Filters

  • commentStatus: 'pending' (default) / 'true' (approved) / 'false' (rejected) / all
  • searchAll: Matches against comment ID, blog title, commenter name, or comment content
  • searchMail: Email address filter
  • date_start: Date range filter

Comment Data Fields (blog_comments table)

  • blog_id (FK to article)
  • name, email (commenter info)
  • comment_content
  • status (pending / approved / rejected)
  • entry_date
  • lang

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)

MethodHTTPAction
addProduct()POSTAdd single product to article with priority
deleteProduct()DELETERemove single product from article
repositionProducts()POSTBatch 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_product table: blog-side (managed by Adv_blog_product_model)
  • product_blog table: product-side (managed by Adv_product_blog_model in ecommercen/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

TableTypeDescription
blogMasterArticle records
blog_muiMUIArticle multi-language content
blog_categoriesMasterCategory hierarchy
blog_categories_muiMUICategory multi-language content
blog_authorMasterAuthor profiles
blog_author_muiMUIAuthor multi-language content
blog_tagsMasterTags with ordering
blog_tags_muiMUITag multi-language content with hex color
blog_blog_categoriesLink-pairArticle-to-category many-to-many
blog_blog_tagsLink-pairArticle-to-tag many-to-many
blog_productLink-pairArticle-to-product with priority
product_blogLink-pairProduct-to-article (reverse sync)
blog_commentsFlatUser-submitted comments with moderation status
blog_defaultMasterDefault blog page settings
blog_default_muiMUIDefault blog page MUI content

Registry Settings

GroupKeyEffect
ESHOPBLOG_PRODUCT_ENABLEDEnables blog-product linking feature
ESHOPBLOG_PRODUCT_TWO_WAY_SYNCEnables bidirectional sync between blog_product and product_blog
ESHOPBLOG_PRODUCT_CATEGORY_ENABLEDEnables blog-product-category cross-linking
OTHERENABLE_BLOG_COMMENTSEnables comment submission and moderation
EMAIL_SUBJECTSBLOG_COMMENT_ACCEPTEmail subject for comment approval notification
EMAIL_SUBJECTSBLOG_COMMENT_REJECTEmail subject for comment rejection notification
AI_CONTENT_GENERATION_PROVIDER_OPEN_AIIS_ENABLEDEnables 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.