Appearance
Are you an LLM? You can read better optimized documentation at /flows/admin/AD-15-attributes-tags.md for this page in Markdown format
Attributes & Tags Management (Admin)
Flow ID: AD-15 Module(s): eshop, attributes Complexity: Medium Last Updated: 2026-04-04
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_attributesjunction 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
| Table | Purpose | Key Columns |
|---|---|---|
attribute_groups | Attribute group (e.g., "Color", "Size") | id, display_type |
attribute_groups_mui | MUI translations per group | attribute_group_id, name, lang |
attribute_values | Individual values within a group | id, attribute_group_id, display_value, active |
attribute_values_mui | MUI translations per value | attribute_value_id, name, lang |
product_code_attributes | Junction: product code to attribute value | product_code_id, attribute_value_id |
Display types (defined in ecommercen/helpers/shopmodule_helper.php:857):
text-- plain text display valuehex-color-- CSS hex color code (rendered as color swatch)image-- uploaded image file (stored infiles/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:
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.- 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_typerequired;name_{lang}required per admin language - Value:
name_{lang}required per admin language;display_valueoptional;activeoptional;idrequired for update - Image upload validation delegated to
advuploaderlibrary (image type values only)
Deletion guards:
attribute_groups_model->canDelete($id): blocked if anyattribute_valuesreference the group (and currently hardcoded tofalsevia TODO)attribute_values_model->canDelete($id): blocked if anyproduct_code_attributesreference the value (also hardcoded tofalse)
1.3 Vue SPA Components
Entry point: assets/admin/js/attributes-page.js -- mounts AdminAttributesPage component.
| Component | File | Purpose |
|---|---|---|
AdminAttributesPage | assets/admin/js/attributes/AdminAttributesPage.vue | Root component: state management, API calls, view routing |
AttributeGroupList | assets/admin/js/attributes/AttributeGroupList.vue | Table of groups with edit/values actions |
AttributeGroupForm | assets/admin/js/attributes/AttributeGroupForm.vue | Create/edit group with MUI tabs and type dropdown |
AttributeValuesList | assets/admin/js/attributes/AttributeValuesList.vue | Table of values within a group, color swatch display |
AttributeValuesForm | assets/admin/js/attributes/AttributeValuesForm.vue | Create/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 renderingAttributeValueDTO-- 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()callsprocessProductCodes()which writes to theproduct_code_attributesjunction tableAdv_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 JOINsproduct_code_attributesper group
1.6 REST API (Modern Layer)
Domain: src/Domains/Product/Attribute/
| Entity | Table | Key Properties |
|---|---|---|
Group\Repository\Entity | attribute_groups | id, display_type |
Group\Repository\MuiEntity | attribute_groups_mui | attribute_group_id, name, lang |
Attribute\Repository\Entity | attribute_values | id, attribute_group_id, display_value, active |
Attribute\Repository\MuiEntity | attribute_values_mui | attribute_value_id, name, lang |
REST Controllers: src/Rest/Product/Controllers/
| Controller | Endpoint | Methods |
|---|---|---|
AttributeGroup | /rest/product/attribute-group | GET (index, show, item), POST (store, update), DELETE (destroy) |
Attribute | /rest/product/attribute | GET (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
| Table | Purpose | Key Columns |
|---|---|---|
shop_product_tag_categories | Tag group/category | id, tag_category_image, tag_category_behavior, tag_values_behavior, order |
shop_product_tag_categories_mui | MUI for tag categories | tag_cat_id, slug, name, content, lang |
shop_product_tags | Individual tags | id, tag_cat_id, tag_image, order |
shop_product_tags_mui | MUI for tags | tag_id, slug, name, content, lang |
shop_product_product_tags | Junction: product to tag | id, product_id, tag_id |
shop_product_category_group_tags_lp | Junction: product category to tag group | category_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):
| Field | Value 0 | Value 1 | Effect on Filtering |
|---|---|---|---|
tag_category_behavior | AND | OR | How this category combines with OTHER tag categories |
tag_values_behavior | AND | OR | How 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
| Method | Purpose |
|---|---|
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
| Method | Purpose |
|---|---|
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) callsproduct_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_tagsaction:product_tags_model->assignProductsToTags($products, $tagIds)-- adds tags without removing existing ones, deduplicating against current assignmentsdelete_tagaction: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 fromshop_product_category_group_tags_lpgetCategoriesTagGroups($categoryId)walks up the category tree to inherit parent assignments if none are set at the current levelAdv_product_category_modelline 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/
| Entity | Table | Key Properties |
|---|---|---|
Category\Repository\Entity | shop_product_tag_categories | id, tag_category_image, tag_category_behavior, tag_values_behavior, order |
Category\Repository\MuiEntity | shop_product_tag_categories_mui | tag_cat_id, slug, name, content, lang |
Tag\Repository\Entity | shop_product_tags | id, tag_cat_id, tag_image, order |
Tag\Repository\MuiEntity | shop_product_tags_mui | tag_id, slug, name, content, lang |
REST Controllers: src/Rest/Product/Controllers/
| Controller | Endpoint | Methods | Upload Support |
|---|---|---|---|
TagCategory | /rest/product/tag-category | GET, POST, DELETE | Yes (tag_category_image to files/product_tag_category/) |
Tag | /rest/product/tag | GET, POST, DELETE | Yes (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
| Table | Purpose | Key Columns |
|---|---|---|
shop_order_tags | Flat tag list | id, title, slug |
shop_order_tags_lp | Junction: order to tag | order_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
| Method | Purpose |
|---|---|
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, deduplicatesunassignTagsFromOrders()-- bulk removeresetAndAssignTagsToOrders()-- replace all tags for orders (transactional)getOrderTags($orderId)-- returns[id => title]mapgetTagsForOrders($orders)-- enriches order objects with->tagsproperty
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.phpecommercen/eshop/models/Adv_attribute_groups_model.phpecommercen/eshop/models/Adv_attribute_values_model.phpecommercen/attributes/DTO/AttributeProductDTO.phpecommercen/attributes/DTO/AttributeValueDTO.phpassets/admin/js/attributes-page.jsassets/admin/js/attributes/AdminAttributesPage.vue
Legacy Admin (Tags)
ecommercen/eshop/controllers/Adv_product_tag_categories_admin.phpecommercen/eshop/controllers/Adv_product_tags_admin.phpecommercen/eshop/controllers/Adv_product_tags.php(storefront)ecommercen/eshop/models/Adv_product_tag_categories_model.phpecommercen/eshop/models/Adv_product_tags_model.phpecommercen/eshop/models/Adv_product_tag_lp_model.php
Legacy Admin (Order Tags)
ecommercen/eshop/controllers/Adv_order_tags_admin.phpecommercen/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.phpsrc/Rest/Product/Controllers/Attribute.phpsrc/Rest/Product/Controllers/TagCategory.phpsrc/Rest/Product/Controllers/Tag.phpsrc/Rest/Order/Controllers/OrderTag.phpsrc/Rest/Product/Resources/AttributeGroup/Resource.phpsrc/Rest/Product/Resources/Attribute/Resource.php(not shown, same pattern)src/Rest/Product/Resources/TagCategory/Resource.phpsrc/Rest/Product/Resources/Tag/Resource.php
Configuration
application/config/routes.php(lines 346-349: attribute admin; lines 85-99: tag front; lines 729-733: order tags)application/config/rest_routes.php(lines 396-409: attribute REST; lines 427-439: tag REST; lines 749-762: order tag REST; lines 1357-1362: attribute write; lines 1557-1570: 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())
Related Flows
- AD-02 Product Management -- product-level tag/attribute assignment
- AD-03 Order Management -- order tag assignment
- AD-05 Category Management -- category-to-tag-group filtering
- AD-16 Variations -- complementary dimension system (variations at product level vs. attributes at SKU level)
- AD-28 Order Tags -- order tag CRUD and assignment (detailed in dedicated flow)
- CF-01 Category Listing -- storefront tag/attribute filtering
- CF-13 Coupons -- tag-based coupon targeting
- CF-18 Product Tags -- storefront tag pages at
/tag/{slug} - SY-23 MUI Translation Pattern --
attribute_groups_mui,attribute_values_mui,shop_product_tags_mui, andshop_product_tag_categories_muicompanion tables store per-language names and slugs - SY-25 File Upload & Storage --
advuploaderhandles tag and attribute value image uploads tofiles/product_attributes/andfiles/product_tag/