Skip to content

Product Variations Management (Admin) ​

Flow ID: AD-16 Module(s): variations Complexity: High Last Updated: 2026-09-29

Business Context ​

Product variations allow grouping products by shared dimensions (e.g., Size, Color, Material) so customers can switch between related products on the storefront. The admin panel provides three management areas: variation group definitions (the dimension types), variation values (the options within each dimension), and product variation groups (which products belong together and which is the master). The entire feature is gated by the enableProductVariations config flag (default: false). When disabled, variation menu items are hidden and product-level variation UI is suppressed.

Architecture: Entirely legacy HMVC -- no modern Domain/REST layer exists for admin variations. The HMVC module lives in ecommercen/variations/ with controllers, models, DTOs, and helpers. A separate API controller (AdvApiVariationsController) serves the Vue frontend for product-level variation editing. The admin Vue app (AdminVariationsPage) manages groups/values, while AdminVariationProductsPage handles per-product variation assignment within the product edit form.


Database Schema ​

6 Tables ​

TablePurposeKey Columns
variation_groupsDimension definitions (Size, Color, etc.)id, display_type, step, display_position, priority, force_display_all_values
variation_groups_muiMUI names per languagevariation_group_id, name, lang
variation_valuesOptions within a groupid, variation_group_id, display_value, priority
variation_values_muiMUI names per languagevariation_value_id, name, lang
product_variationsProduct-to-group assignmentsid, group_id, product_id, is_master, priority
product_variations_muiMUI names for product variation groupsgroup_id, name, lang
product_variation_valuesProduct-to-variation-value linksproduct_id, variation_id (unique composite)

Key relationships: variation_values.variation_group_id -> variation_groups.id; product_variations.group_id is an auto-incremented group identifier (not an FK to variation_groups); product_variation_values.variation_id -> variation_values.id.

Important distinction: variation_groups defines dimension types (e.g., "Size"), while product_variations.group_id defines product groupings (e.g., "Nike Air Max Family"). These are separate concepts with separate MUI tables.


Feature Gate ​

Config: $config['enableProductVariations'] = false; in application/config/app.php

When disabled:

  • Admin menu hides the "variations" route via AdminMenu::parseProductVariationMenu()
  • AdvVariationsProductsAdmin and AdvVariationsPageAdmin redirect to advisable
  • Product edit form hides the <admin-variation-products-page> component
  • Batch update actions assign_products_to_variation_group and assign_products_to_new_variation_group are suppressed

Authorization ​

Base class: AdvVariationsAdminBase extends Admin_c, requires roles: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, or AUTH_ROLE_PRODUCTS.

All four admin controllers inherit this base and share the same role check. The API controller (AdvApiVariationsController) extends Front_c but gates itself in its own _remap() (ecommercen/api/controllers/AdvApiVariationsController.php:22-35): every endpoint, read or write, requires a logged-in admin session holding one of the same three roles (isAuthorisedAdminCaller(), :40-52); any other caller gets HTTP 401 {"error":"Unauthorized"} and the action never runs. Unknown or non-public method names return 404 to authorised callers (isDispatchableMethod(), :67-79).


Area 1: Variation Groups (Dimension Types) ​

Controller: ecommercen/variations/controllers/AdvVariationsAdmin.phpPage controller: ecommercen/variations/controllers/AdvVariationsPageAdmin.phpModel: ecommercen/variations/models/AdvVariationGroupsModel.phpView: application/views/admin/variations/vue.php (Vue SPA mount point) Vue component: assets/admin/js/variations/AdminVariationsPage.vueRoute: /variations/variations

Display Types ​

Defined in AdvVariationsHelper::getVariationTypes():

KeyDescriptionFrontend rendering
textPlain text labelsText buttons/pills
hex-colorColor swatchesVariationHexSelector.vue renders color circles
imageImage thumbnailsVariationImageSelector.vue renders image pills
text-range-per-valueNumeric ranges by valueGroups values into ranges based on display_value and step
text-range-per-countNumeric ranges by countGroups values into ranges of step items each

Display Positions ​

Defined in AdvVariationsHelper::getVariationGroupDisplayPositionTypes():

ValueMeaning
0ALL -- show on product page and in category filters
1PRODUCT only -- show on product page, hide from filters
2FILTER only -- show in category filters, hide from product page
3NONE -- hidden everywhere (data-only)

CRUD Operations ​

Add (POST /variations/variations/add):

  1. Validates: display_type (required), step (numeric), display_position, priority, force_display_all_values
  2. Validates MUI: name_{lang} required for each admin language
  3. AdvVariationGroupsModel::add() inserts into variation_groups + variation_groups_mui
  4. Returns JSON {id} on success

Edit (POST /variations/variations/edit):

  1. Same validation + requires id
  2. AdvVariationGroupsModel::update() updates master row + upserts MUI via updateOrInsertMui()
  3. Returns JSON {id} on success

Order (POST /variations/variations/order):

  1. Accepts JSON array of {id, priority} pairs
  2. AdvVariationGroupsModel::order() batch-updates priority column

Delete: Handled by AdvVariationGroupsModel::delete() -- checks canDelete() first: a group cannot be deleted if it has child variation values. Deletion cascades to variation_groups_mui within a transaction.

Page Data Endpoint ​

GET /variations/variations/getPageData returns all data needed by the Vue SPA:

  • variationGroups -- all variation_groups rows
  • variationGroupsMui -- all variation_groups_mui rows
  • variationValues -- all values joined with group info
  • variationValuesMui -- all value MUI for current language
  • variationTypes -- display type options
  • variationGroupDisplayPositionTypes -- position type options
  • configs, userRoles, configLanguages, translations

Area 2: Variation Values (Options) ​

Controller: ecommercen/variations/controllers/AdvVariationsValuesAdmin.phpModel: ecommercen/variations/models/AdvVariationValuesModel.phpRoute: /variations/values/{action}

Values are managed inline within the groups Vue page. Clicking a group opens its values list (VariationValuesList.vue), then values can be added/edited via VariationValuesForm.vue.

CRUD Operations ​

Add (POST /variations/values/add):

  1. Validates: display_value (trim), MUI name_{lang} (required per language)
  2. Validates variation_group_id exists via AdvVariationGroupsModel::getMain()
  3. AdvVariationValuesModel::add() inserts into variation_values + variation_values_mui

Edit (POST /variations/values/edit):

  1. Same validation + requires id
  2. AdvVariationValuesModel::update() updates master + upserts MUI

Order (POST /variations/values/order):

  1. Accepts JSON array of {variation_value_id, priority} pairs
  2. Batch-updates priority column

Delete (POST /variations/values/delete):

  1. Accepts variation value ID as JSON
  2. canDelete() check: if the value is referenced in product_variation_values, deletion is blocked and the referencing product IDs are returned
  3. On success: deletes from variation_values_mui + variation_values in a transaction

Batch Actions on Values ​

Two batch actions available via BatchAction.vue when values are selected:

Move to Another Group (BatchActionMoveValueToAnotherGroup.vue):

  • Calls api/variations/batchActionMoveVariationValues
  • Model: AdvVariationModel::moveVariationValuesToAnotherGroup() -- raw SQL UPDATE with JOIN to reassign variation_values.variation_group_id
  • Confirmation via SweetAlert2; page reloads after success

Mass Delete Values (BatchActionMassDeleteValues.vue):

  • Calls api/variations/batchActionMassDeleteValues
  • Model: AdvVariationModel::massDeleteVariationValues() -- deletes from both variation_values and variation_values_mui
  • No referential integrity check (unlike single-value delete) -- caution: can orphan product_variation_values rows

Area 3: Product Variation Groups ​

Controller: ecommercen/variations/controllers/AdvVariationsProductsAdmin.phpModel: ecommercen/variations/models/AdvVariationModel.phpViews: application/views/admin/variations/list.php, group.php, group_create.phpVue entry: assets/admin/js/variations/store/variations-page.jsRoutes: /variations/products, /variations/products/create, /variations/products/edit/{groupId}, /variations/products/delete/{groupId}

Product variation groups link multiple products together (e.g., same shoe in different colors). Each group has a MUI name and a designated master product.

List Page ​

AdvVariationsProductsAdmin::index():

  • Paginated list (20 per page) from product_variations_mui for current language
  • Displays group ID, name, edit/delete actions
  • Server-rendered table (no Vue)

Create ​

AdvVariationsProductsAdmin::create():

  • Renders group_create.php -> mounts AdminProductVariationGroup Vue component
  • Vue receives JSON state: configLanguages, allVariation (all variation values with groups), allAttributes, isNewGroup=true
  • On submit: parses variationProducts (product IDs with serialized variation selections), variationGroupName (MUI), masterProductId
  • AdvVariationModel::createVariationProductGroup():
    1. Begins transaction
    2. Auto-generates new group_id via createNewGroupId() (SELECT MAX + 1)
    3. For each product: clears any existing variation data, inserts into product_variations and product_variation_values
    4. Inserts MUI names into product_variations_mui
    5. Commits or rolls back

Edit ​

AdvVariationsProductsAdmin::edit($groupId):

  • Loads existing group data via getVariationProductsByGroupId() -> returns VariationGroupProductDTO[]
  • Renders group.php -> mounts AdminProductVariationGroup Vue component with existing data
  • On submit: processAllVariationGroupProducts():
    1. For each product in POST: delete-then-reinsert pattern (processProductVariation)
    2. Removes products no longer in the submitted list via removeOtherProductIds()
    3. Updates MUI names separately

Delete ​

AdvVariationsProductsAdmin::delete($groupId):

  • deleteProductVariationGroup() removes all product_variations and product_variations_mui rows for the group in a transaction
  • Note: does NOT clean up product_variation_values for affected products

Master Product Management ​

Each product group designates one product as master (is_master = 1). The master product:

  • Appears as the primary product in category listings (non-master variants are hidden via ProductVariationsListingTrait)
  • Serves as the canonical URL/display product

updateGroupMaster(): Transactional unset-all then set-one pattern. setAnotherProductAsMaster(): When removing the current master, auto-promotes the next product by priority.


Area 4: Per-Product Variation Assignment (Product Edit Page) ​

Vue component: assets/admin/js/products/variations/AdminVariationProductsPage.vueAPI controller: ecommercen/api/controllers/AdvApiVariationsController.phpApplication wrapper: application/modules/api/controllers/Api_variations.phpRoutes: /api/variations/{action}

This component appears as a tab in the product edit form (when enableProductVariations is true). It allows:

  1. Selecting which variation groups apply to this product (multiselect dropdown)
  2. Choosing specific variation values per group (via VariationGroupQuickActions)
  3. Viewing/managing the product's variation group (modal with full group management)

Internal API Endpoints ​

MethodActionPurpose
POSTgetVariationGroupProductsApiGet all products in a product's variation group
POSTupdateProductVariationSave variation value selections for a product
POSTupdateVariationGroupMasterChange master product in a group
POSTgetAllVariationGet all variation values with group info
POSTgetVariationGroupMuiGet MUI names for a group
POSTgetVariationGroupNameGet group name by product ID
POSTgetVariationGroupIdGet group ID by product ID
POSTgetProductVariationsGet variation data for multiple product IDs
POSTdeleteProductFromVariationGroupRemove product from its group
POSTaddVariationProductToGroupAdd product to existing group, or create new group
POSTbatchActionMoveVariationValuesMove variation values between groups
POSTchangeVariationGroupProductsPriorityReorder products within a group
POSTbatchActionMassDeleteValuesBulk delete variation values
POSTupdateProductVariationGroupNameMuiUpdate group MUI names
POSTupdateProductStockUpdate stock for a product code (SKU)
POSTgetVariationByVariationValueIdLookup variation details by value ID

Validation Rules ​

All endpoints validate via CI form_validation. Responses follow sendOutput()/sendError() pattern:

  • Unauthorized (no admin session or missing role, checked before validation): {"error":"Unauthorized"} with 401
  • Success: {data} with 200
  • Validation error: {errors} with 400
  • Business rule violation: {error} with 422 (e.g., product already in another group, empty variation group names)

Area 5: Batch Product Assignment (Product Listing) ​

Controller: ecommercen/eshop/controllers/Adv_products_admin.php (batch update actions) View: application/views/admin/products/batch_update.php

Two batch actions on the product listing page:

Assign to Existing Group ​

Action: assign_products_to_variation_group

  1. Admin selects products and chooses an existing variation group from dropdown
  2. batchActionDropdown() provides the group combo (name + group_id from product_variations_mui)
  3. Pre-check: if any selected product already belongs to a variation group, the entire batch is rejected with error message
  4. batchAssignProductsToVariationGroup(): insert_batch into product_variations (all as non-master)

Assign to New Group ​

Action: assign_products_to_new_variation_group

  1. Admin selects products, provides MUI group names per language
  2. If any products are already in groups, manageExistingProductsInGroups() handles removal:
    • If product is master and group has >1 member: auto-promotes another member, then removes
    • If product is the only member: deletes entire group
    • If product is non-master: just removes from group
  3. batchAssignProductsToNewVariationGroup(): creates new group_id, inserts all products (first product becomes master), inserts MUI names

DTOs ​

Three DTOs in ecommercen/variations/DTO/ provide typed data transfer:

VariationGroupProductDTO: Represents a product within a variation group with its assigned variation values. Properties: id, product_id, group_id, variation_value_id, variation_group_id, variation_value_name, variation_group_name, is_master, priority.

VariationProductDTO: Represents a variation group with all its values for a product. Properties: variationGroupName, variationGroupId, variationDisplayType, variationGroupStep, variationGroupDisplayPosition, variationGroupOrder, variationGroupForceDisplayAllValues, variations[] (array of VariationValueDTO), variationProductVariationId, variationProductVariationGroupId.

VariationValueDTO: Represents a single variation value. Properties: variationId, variationName, variationDisplayValue, variationOrder.


Frontend Architecture (Admin Vue) ​

Variation Groups/Values Page ​

Entry point: assets/admin/js/variations/store/variations-page.js mounts the Vue app.

Component tree:

AdminVariationsPage
  ├── VariationGroupList     (list all groups, drag-to-reorder, click to open values)
  ├── VariationGroupForm     (add/edit group with display type, step, position, MUI names)
  ├── VariationValuesList    (list values for selected group, drag-to-reorder, batch actions)
  │   └── BatchAction
  │       ├── BatchActionMoveValueToAnotherGroup
  │       └── BatchActionMassDeleteValues
  └── VariationValuesForm    (add/edit value with display_value and MUI names)

Product Variation Group Page ​

Entry point: same variations-page.js, also mounts AdminProductVariationGroup.

Component tree:

AdminProductVariationGroup
  ├── AdminProductVariationGroupSearch     (product search to add)
  ├── AdminProductVariationGroupList       (product cards in group)
  │   └── AdminProductVariationGroupCard   (individual product with variation selectors)
  │       ├── AdminProductVariationGroupVariations  (variation value dropdowns)
  │       ├── AdminProductVariationGroupMaster      (master toggle)
  │       └── AdminProductVariationGroupActions     (remove, stock edit, priority)
  └── AdminProductVariationGroupInput      (hidden inputs for form submission)

Per-Product Variation (Product Edit Page) ​

Component: AdminVariationProductsPage.vue (mounted in product edit template). Uses Vuex stores: product (product store) and variation (variation store via variationStore.js).


Storefront Integration ​

Listing Behavior ​

ProductVariationsListingTrait (used by category/vendor/search controllers):

  • processProductsQueryForVariationMaster(): Filters product listings to show only master products; non-master variants are hidden
  • fillVariationMasterInfo(): Adds group name and member count to master product display
  • renderListingVariationGroupCombosJsonData(): Provides variation group combos for listing-level variation selectors

Filter Behavior ​

ProductVariationsListingTrait::initializeVariationFiltersJson():

  • Query string parameter per language: el -> προιοντα-με, en -> products-with
  • Registry setting VARIATIONS.MERGE_DUPLICATE_VALUES_BY_NAME controls whether values with identical names across groups are merged in filters
  • Active filters validated against all variation records; invalid combinations trigger 404

Product Page Behavior ​

ProductVariationsTrait::getVariationGroupProducts():

  • Retrieves all products in the same variation group
  • Merges live product data (prices, stock, images, URLs) via mergeProductVariationsWithLiveData()
  • Returns unique products with variation metadata for the Vue variation selector

Registry Settings ​

GroupKeyPurpose
VARIATIONSMERGE_DUPLICATE_VALUES_BY_NAMEWhen true, variation values with the same name across groups are merged in filter display

Key Files Reference ​

CategoryPath
Controllersecommercen/variations/controllers/AdvVariationsAdminBase.php
ecommercen/variations/controllers/AdvVariationsAdmin.php
ecommercen/variations/controllers/AdvVariationsValuesAdmin.php
ecommercen/variations/controllers/AdvVariationsProductsAdmin.php
ecommercen/variations/controllers/AdvVariationsPageAdmin.php
API Controllerecommercen/api/controllers/AdvApiVariationsController.php
App Wrapperapplication/modules/api/controllers/Api_variations.php
Modelsecommercen/variations/models/AdvVariationGroupsModel.php
ecommercen/variations/models/AdvVariationValuesModel.php
ecommercen/variations/models/AdvVariationModel.php
DTOsecommercen/variations/DTO/VariationGroupProductDTO.php
ecommercen/variations/DTO/VariationProductDTO.php
ecommercen/variations/DTO/VariationValueDTO.php
Helperecommercen/variations/helpers/AdvVariationsHelper.php
Traitsecommercen/eshop/traits/ProductVariationsTrait.php
ecommercen/eshop/traits/ProductVariationsListingTrait.php
Viewsapplication/views/admin/variations/vue.php
application/views/admin/variations/list.php
application/views/admin/variations/group.php
application/views/admin/variations/group_create.php
Vue (Groups/Values)assets/admin/js/variations/AdminVariationsPage.vue
assets/admin/js/variations/VariationGroupList.vue
assets/admin/js/variations/VariationGroupForm.vue
assets/admin/js/variations/VariationValuesList.vue
assets/admin/js/variations/VariationValuesForm.vue
Vue (Batch Actions)assets/admin/js/variations/batchActions/BatchAction.vue
assets/admin/js/variations/batchActions/value/BatchActionMoveValueToAnotherGroup.vue
assets/admin/js/variations/batchActions/value/BatchActionMassDeleteValues.vue
Vue (Product Groups)assets/admin/js/variations/productGroups/AdminProductVariationGroup.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupList.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupCard.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupSearch.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupMaster.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupActions.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupVariations.vue
assets/admin/js/variations/productGroups/AdminProductVariationGroupInput.vue
Vue (Per-Product)assets/admin/js/products/variations/AdminVariationProductsPage.vue
assets/admin/js/products/variations/ProductVariationInput.vue
assets/admin/js/products/variations/VariationGroupQuickActions.vue
Vuex Storeassets/admin/js/variationStore.js
assets/admin/js/variations/store/state.js
assets/admin/js/variations/store/actions.js
assets/admin/js/variations/store/mutations.js
assets/admin/js/variations/store/getters.js
assets/admin/js/variations/store/variations-page.js
Routesapplication/config/routes.php (lines 353-374, 578-581)
Configapplication/config/app.php (enableProductVariations)
Admin Menuapplication/libraries/AdminMenu.php (parseProductVariationMenu)

Known Issues & Security Gaps ​

  1. Unauthenticated /api/variations/* exposure — Fixed (#850). AdvApiVariationsController::_remap() now gates every routable method behind an admin session (ecommercen/api/controllers/AdvApiVariationsController.php:22-35): callers must be logged in and hold AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN or AUTH_ROLE_PRODUCTS (:40-52, else a 401 is sent), and _remap() returns a 404 when isDispatchableMethod() (:67-79) rejects the method (non-existent, _-prefixed, non-public, static, constructor or inherited CI_Controller method).

No open issues were found in this resync.