Skip to content

Attributes & Tags Management (Admin) ​

Flow ID: AD-15 Module(s): eshop, attributes Complexity: Medium Last Updated: 2026-09-14

Business Context ​

The platform uses two distinct faceted systems for product classification and storefront filtering:

  • Attributes define product-code-level properties (color, size, material) linked to individual SKUs via the product_code_attributes junction table. They drive variant selection on product detail pages.
  • Product Tags are product-level labels organized into tag categories (groups). They power sidebar/URL-based faceted filtering on category listing pages and also serve as targeting criteria for coupons (CF-13) and feeds.
  • Order Tags are a separate flat tag system for internal order classification (AD-28, covered briefly here for completeness).

Both attributes and product tags have full CRUD in the legacy admin panel and modern REST API with OpenAPI documentation.


1. Product Attributes ​

1.1 Data Model ​

TablePurposeKey Columns
attribute_groupsAttribute group (e.g., "Color", "Size")id, display_type
attribute_groups_muiMUI translations per groupattribute_group_id, name, lang
attribute_valuesIndividual values within a groupid, attribute_group_id, display_value, active
attribute_values_muiMUI translations per valueattribute_value_id, name, lang
product_code_attributesJunction: product code to attribute valueproduct_code_id, attribute_value_id

Display types (defined in ecommercen/helpers/shopmodule_helper.php:857):

  • text -- plain text display value
  • hex-color -- CSS hex color code (rendered as color swatch)
  • image -- uploaded image file (stored in files/product_attributes/)

1.2 Admin Controller (Legacy) ​

Controller: ecommercen/eshop/controllers/Adv_attributes_admin.php (438 lines) Auth: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTSRoute: /eshop/attributes_admin (aliased to /attributes_admin)

This is a Vue 2 SPA -- unlike most admin controllers, it does NOT use server-rendered views for CRUD. Instead:

  1. index() renders a single Vue mount point (#attribute-page) and injects all data as a JSON blob (jsonState) containing: attribute groups, groups MUI, attribute values, values MUI, display types, languages, user roles, and translations.
  2. All CRUD operations are AJAX JSON endpoints consumed by the Vue components:
    • add() -- create attribute group (POST, JSON response)
    • edit() -- update attribute group (POST, JSON response)
    • addValue() -- create attribute value within a group (POST, supports multipart for image type)
    • editValue() -- update attribute value (POST, supports multipart for image type)
    • getPageData() -- reload all state after mutations (GET, returns full JSON blob)

Validation rules:

  • Group: display_type required; name_{lang} required per admin language
  • Value: name_{lang} required per admin language; display_value optional; active optional; id required for update
  • Image upload validation delegated to advuploader library (image type values only)

Deletion guards:

  • attribute_groups_model->canDelete($id): blocked if any attribute_values reference the group (and currently hardcoded to false via TODO)
  • attribute_values_model->canDelete($id): blocked if any product_code_attributes reference the value (also hardcoded to false)

1.3 Vue SPA Components ​

Entry point: assets/admin/js/attributes-page.js -- mounts AdminAttributesPage component.

ComponentFilePurpose
AdminAttributesPageassets/admin/js/attributes/AdminAttributesPage.vueRoot component: state management, API calls, view routing
AttributeGroupListassets/admin/js/attributes/AttributeGroupList.vueTable of groups with edit/values actions
AttributeGroupFormassets/admin/js/attributes/AttributeGroupForm.vueCreate/edit group with MUI tabs and type dropdown
AttributeValuesListassets/admin/js/attributes/AttributeValuesList.vueTable of values within a group, color swatch display
AttributeValuesFormassets/admin/js/attributes/AttributeValuesForm.vueCreate/edit value with MUI tabs, image upload/preview, hex input, active toggle

Navigation flow: Group List --> (edit) Group Form | (values) Values List --> (add/edit) Values Form. The showValues and isGroupFormOpen/isValuesFormOpen flags control which panel is visible. Data is reloaded from getPageData after every mutation.

1.4 DTO Layer ​

ecommercen/attributes/DTO/ contains two DTOs used by the product model for structured attribute data:

  • AttributeProductDTO -- represents an attribute group with its values and display type, used when loading attributes for product admin forms and storefront rendering
  • AttributeValueDTO -- represents a single attribute value (id, name, display value)

These are consumed by Adv_attribute_groups_model::getAttributeGroupsWithValues() and getAttributeGroupCombosForProductCodeIds().

1.5 Product Integration ​

When creating or editing a product, attributes are assigned at the product-code level:

  • Adv_products_admin::manageProductAttributes() calls processProductCodes() which writes to the product_code_attributes junction table
  • Adv_attribute_values_model::getAttributesValuesGroupsCombo() provides the attribute group/value dropdown for the product form
  • Attribute filtering on category pages is handled by Adv_product_model::queryForListingAttributeFilters() which JOINs product_code_attributes per group

1.6 REST API (Modern Layer) ​

Domain: src/Domains/Product/Attribute/

EntityTableKey Properties
Group\Repository\Entityattribute_groupsid, display_type
Group\Repository\MuiEntityattribute_groups_muiattribute_group_id, name, lang
Attribute\Repository\Entityattribute_valuesid, attribute_group_id, display_value, active
Attribute\Repository\MuiEntityattribute_values_muiattribute_value_id, name, lang

REST Controllers: src/Rest/Product/Controllers/

ControllerEndpointMethods
AttributeGroup/rest/product/attribute-groupGET (index, show, item), POST (store, update), DELETE (destroy)
Attribute/rest/product/attributeGET (index, show, item), POST (store, update), DELETE (destroy)

Filters: id, displayType (groups); id, attributeGroupId, active, name.{locale} (values) Relations: translations, attributes (group has many); translations, group (value belongs to)

DI registration: src/Rest/Product/container.php lines 45-57


2. Product Tags ​

2.1 Data Model ​

TablePurposeKey Columns
shop_product_tag_categoriesTag group/categoryid, tag_category_image, tag_category_behavior, tag_values_behavior, order
shop_product_tag_categories_muiMUI for tag categoriestag_cat_id, slug, name, content, lang
shop_product_tagsIndividual tagsid, tag_cat_id, tag_image, order
shop_product_tags_muiMUI for tagstag_id, slug, name, content, lang
shop_product_product_tagsJunction: product to tagid, product_id, tag_id
shop_product_category_group_tags_lpJunction: product category to tag groupcategory_id, group_tag_id

2.2 Behavior Operators ​

Each tag category has two behavior flags controlling how tags within it combine during storefront filtering (defined in ecommercen/helpers/theme_helper.php:118):

FieldValue 0Value 1Effect on Filtering
tag_category_behaviorANDORHow this category combines with OTHER tag categories
tag_values_behaviorANDORHow tags WITHIN this category combine with each other

These behaviors drive Adv_product_model::queryForListingTagFilters() which constructs dynamic JOINs: AND categories use INNER JOIN, OR categories use LEFT JOIN. The tagFiltersPrepare() method resolves tag slugs (format: category-slug/tag-slug) into structured filter objects with behavior metadata.

2.3 Tag Categories Admin Controller ​

Controller: ecommercen/eshop/controllers/Adv_product_tag_categories_admin.php (255 lines) Auth: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTSRoute: /product_tag_categories_admin

MethodPurpose
index()List with search by behavior type; drag-and-drop ordering
add()Create with image upload, MUI (slug, name, content), behavior selectors
edit($id)Update all fields; slug editable only by ADVISABLE role; auto-generates slug from name if missing
delete($id)Blocked if tag category has any child tags
updateOrder()AJAX drag-and-drop reorder

Validation: name_{lang} required per language; content_{lang} optional; slug_{lang} required on update for ADVISABLE role.

Deletion guard: product_tag_categories_model->delete() checks for child records in shop_product_tags; sets session error if tags exist.

2.4 Tags Admin Controller ​

Controller: ecommercen/eshop/controllers/Adv_product_tags_admin.php (326 lines) Auth: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTSRoute: /product_tags_admin

MethodPurpose
index()List with session-persisted search (filter by tag group); shows category name, slug, image
add()Create: image upload to files/product_tag/, MUI (slug auto-generated from name, name, content), tag category assignment
edit($id)Update; slug editable only by ADVISABLE role; auto-fills slug from name if blank
delete($id)Blocked if products are assigned to the tag
products($tagId)View products assigned to a tag with their other tag assignments
product_delete($lookUpId, $tagId)Remove single product-tag association
batchRemoveFromTag($tagId)Bulk remove selected products from a tag
updateOrder()AJAX drag-and-drop reorder
resetIndex()Clear search session data

Validation: tag_cat_id must be > 0; name_{lang} required; content_{lang} optional; slug_{lang} required on update for ADVISABLE role.

Deletion guard: product_tags_model->delete() checks shop_product_product_tags for assigned products; sets session error if found.

2.5 Product Integration ​

Tags are assigned to products in two ways:

Individual product edit (Adv_products_admin):

  • manageProductTags() (line 581-586) calls product_tag_lp_model->update($productId, $tagIds) which performs a diff-based sync: inserts new tags, removes unselected tags.

Batch operations (Adv_products_admin::batch_action()):

  • set_tags action: product_tags_model->assignProductsToTags($products, $tagIds) -- adds tags without removing existing ones, deduplicating against current assignments
  • delete_tag action: product_tags_model->deleteProductsFromTag($products, $tagId) -- removes specific tag from selected products

2.6 Category-Tag Group Filtering ​

Tag groups can be assigned to product categories to limit which filter groups appear on category pages:

  • Adv_product_tag_categories_model::getCategoryGroupTags($categoryId) returns assigned tag group IDs from shop_product_category_group_tags_lp
  • getCategoriesTagGroups($categoryId) walks up the category tree to inherit parent assignments if none are set at the current level
  • Adv_product_category_model line 1910+ manages the CRUD for this junction via diff-based batch insert/delete
  • Feature gated by registry setting eshop.admin.only_for_advisable.filter_category_group_tags

2.7 Storefront Tag Pages ​

Controller: ecommercen/eshop/controllers/Adv_product_tags.php (183 lines, Front_c) Routes: /tag/{category-slug} resolves to tag_cat(), /tag/{category-slug}/{tag-slug} resolves to tag().

  • tag_cat($slug): Displays list of tags within a tag category (uses PSCache)
  • tag($catSlug, $tagSlug): Displays product listing for a specific tag with breadcrumbs (category > tag)

Routing logic in application/config/routes.php:85-99 uses a closure to determine whether a /tag/ URL has one segment (category) or two (category/tag).

2.8 REST API (Modern Layer) ​

Domain: src/Domains/Product/Tag/

EntityTableKey Properties
Category\Repository\Entityshop_product_tag_categoriesid, tag_category_image, tag_category_behavior, tag_values_behavior, order
Category\Repository\MuiEntityshop_product_tag_categories_muitag_cat_id, slug, name, content, lang
Tag\Repository\Entityshop_product_tagsid, tag_cat_id, tag_image, order
Tag\Repository\MuiEntityshop_product_tags_muitag_id, slug, name, content, lang

REST Controllers: src/Rest/Product/Controllers/

ControllerEndpointMethodsUpload Support
TagCategory/rest/product/tag-categoryGET, POST, DELETEYes (tag_category_image to files/product_tag_category/)
Tag/rest/product/tagGET, POST, DELETEYes (tag_image to files/product_tag/)

Both controllers use HandlesUploadActions trait, accepting application/json or multipart/form-data.

Tag Category relations: translations (one-to-many), tags (one-to-many) Tag relations: translations (one-to-many), category (belongs-to) Tag Category filters: id, order, name.{locale}, slug.{locale}Tag filters: id, priority, name.{locale}, slug.{locale}

DI registration: src/Rest/Product/container.php lines 66-80


3. Order Tags ​

3.1 Data Model ​

TablePurposeKey Columns
shop_order_tagsFlat tag listid, title, slug
shop_order_tags_lpJunction: order to tagorder_id, tag_id

3.2 Admin Controller ​

Controller: ecommercen/eshop/controllers/Adv_order_tags_admin.php (143 lines) Auth: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_ORDERSFeature gate: Registry OTHER.ORDER_TAGS_ENABLED must be truthy Route: /order_tags

MethodPurpose
index($offset)Paginated list with keyword search
add()Create: title (max 25 chars), auto-slug generation
edit($id)Update title and slug
delete($id)Deletes tag and all order associations (transactional)

Uniqueness: titleExists() prevents duplicate titles (excluding current record on update).

3.3 Order Integration ​

Order tags are assigned from the order management interface (AD-03):

  • assignTagsToOrders() -- bulk assign, deduplicates
  • unassignTagsFromOrders() -- bulk remove
  • resetAndAssignTagsToOrders() -- replace all tags for orders (transactional)
  • getOrderTags($orderId) -- returns [id => title] map
  • getTagsForOrders($orders) -- enriches order objects with ->tags property

3.4 REST API ​

Domain: src/Domains/Order/OrderTag/Endpoint: /rest/order/order-tag (GET, POST, DELETE) DI: src/Rest/Order/container.php


Key Files Reference ​

Legacy Admin (Attributes) ​

  • ecommercen/eshop/controllers/Adv_attributes_admin.php
  • ecommercen/eshop/models/Adv_attribute_groups_model.php
  • ecommercen/eshop/models/Adv_attribute_values_model.php
  • ecommercen/attributes/DTO/AttributeProductDTO.php
  • ecommercen/attributes/DTO/AttributeValueDTO.php
  • assets/admin/js/attributes-page.js
  • assets/admin/js/attributes/AdminAttributesPage.vue

Legacy Admin (Tags) ​

  • ecommercen/eshop/controllers/Adv_product_tag_categories_admin.php
  • ecommercen/eshop/controllers/Adv_product_tags_admin.php
  • ecommercen/eshop/controllers/Adv_product_tags.php (storefront)
  • ecommercen/eshop/models/Adv_product_tag_categories_model.php
  • ecommercen/eshop/models/Adv_product_tags_model.php
  • ecommercen/eshop/models/Adv_product_tag_lp_model.php

Legacy Admin (Order Tags) ​

  • ecommercen/eshop/controllers/Adv_order_tags_admin.php
  • ecommercen/eshop/models/Adv_order_tags_model.php

Modern Domain Layer ​

  • src/Domains/Product/Attribute/Group/ (Entity, Repository, Service, WriteService, ListRequest)
  • src/Domains/Product/Attribute/Attribute/ (Entity, Repository, Service, WriteService, ListRequest)
  • src/Domains/Product/Tag/Category/ (Entity, Repository, Service, WriteService, ListRequest)
  • src/Domains/Product/Tag/Tag/ (Entity, Repository, Service, WriteService, ListRequest)
  • src/Domains/Order/OrderTag/ (Entity, Repository, Service, WriteService)

Modern REST Layer ​

  • src/Rest/Product/Controllers/AttributeGroup.php
  • src/Rest/Product/Controllers/Attribute.php
  • src/Rest/Product/Controllers/TagCategory.php
  • src/Rest/Product/Controllers/Tag.php
  • src/Rest/Order/Controllers/OrderTag.php
  • src/Rest/Product/Resources/AttributeGroup/Resource.php
  • src/Rest/Product/Resources/Attribute/Resource.php (not shown, same pattern)
  • src/Rest/Product/Resources/TagCategory/Resource.php
  • src/Rest/Product/Resources/Tag/Resource.php

Configuration ​

  • application/config/routes.php (lines 348-351: attribute admin; lines 85-99: tag front; lines 742-746: order tags)
  • application/config/rest_routes.php (lines 425-439: attribute REST; lines 465-477: tag REST; lines 801-815: order tag REST; lines 1442-1456: attribute write; lines 1650-1664: tag write)
  • src/Rest/Product/container.php (DI for all attribute/tag REST controllers)
  • ecommercen/helpers/shopmodule_helper.php:856 (getAttributeTypes())
  • ecommercen/helpers/theme_helper.php:114 (tagBehaviorOperators())