Appearance
Product Management (Admin)
Flow ID: AD-02 Module(s): eshop, variations, attributes, coupons, badges, video, events, cart_product_customizations Complexity: Very High Last Updated: 2026-06-04
Business Overview
Product management is the largest admin flow in Ecommercen, covering product creation, editing, cloning, soft-deletion, image/media management, and 30+ batch operations. The admin controller (Adv_products_admin) loads 30 models and coordinates complex entity relationships: categories, variations, attributes, tags, product lines, related products, downloads, metadata, promos, feeds, events, videos, bundles, badges, and barcodes.
Products use a master + MUI (multi-language) pattern. The master record holds pricing, stock flags, vendor, VAT, and display settings. Per-language MUI records hold name, descriptions (short, long, extra, ingredients, usage), SEO metadata, slug, and video embed. Product codes (SKUs) are child records that carry stock counts and attribute assignments, with media files attached at the product-code level rather than directly on the product.
The flow serves both the legacy admin panel (CodeIgniter HMVC views) and a modern REST API with full CRUD. Both layers share the same database tables and business rules.
API Reference
REST API (Modern Layer)
| Method | Endpoint | Action | Auth |
|---|---|---|---|
GET | /rest/product/product | List products (paginated, filterable) | JWT |
GET | /rest/product/product/{id} | Get single product by ID | JWT |
GET | /rest/product/product/item | Get single product by filter | JWT |
POST | /rest/product/product | Create product | JWT |
POST | /rest/product/product/{id} | Update product | JWT |
DELETE | /rest/product/product/{id} | Delete product | JWT |
All REST routes support locale-prefixed variants (/{locale}/rest/product/product).
Filters: id, shelfcodeId, vatId, price, vendorId, badgeId, vendorCode (partial), active, softDelete, negativeStock, weight, productView, dateChanged, name.{locale} (partial), slug.{locale} (exact), metaTitle.{locale} (partial), barcode (partial).
Sorts: id, price, vatId, vendorId, active, softDelete, negativeStock, weight, hits, dateChanged, productView, name.{locale}, slug.{locale}.
Relations (via ?with=): translations, vendor, vat, shelfcode, badge, productCodes, barcodes, categories, tags, lines, variationValues, videos, events, articles.
Context-sensitive fields: The Resource class returns a subset of fields for frontend (storefront) contexts. Backend-only fields (exposed only when context->isBackend()) include: shelfcodeId, wholesalePrice, acquisitionValue, cartProductCustomizationSchemaId, cartProductCustomizationRangeFrom/To, vendorCode, active, skroutzName, softDelete, hits, productView, hsCode, dateChanged. negativeStock and pointFactor are both exposed to all contexts (public, customer, and backend): negativeStock so headless consumers can determine true availability when stock is zero; pointFactor is assigned unconditionally in the base data array before the isBackend() guard block and is not gated by context. (src/Rest/Product/Resources/Product/Resource.php)
Routes defined in application/config/rest_routes.php.
Related REST Endpoints (Product Context)
The Product REST context exposes 36 controllers for all product-adjacent entities:
| Endpoint Group | Controller | Description |
|---|---|---|
/rest/product/category | Category | Product categories (CRUD + upload) |
/rest/product/vendor | Vendor | Vendors/brands (CRUD + upload) |
/rest/product/media | Media | Product code media (CRUD) |
/rest/product/attribute-group | AttributeGroup | Attribute groups (CRUD) |
/rest/product/attribute | Attribute | Attribute values (CRUD) |
/rest/product/product-code-attribute | ProductCodeAttribute | SKU-attribute assignments (CRUD) |
/rest/product/tag-category | TagCategory | Tag categories (CRUD + upload) |
/rest/product/tag | Tag | Tags (CRUD + upload) |
/rest/product/supplier | Supplier | Suppliers (CRUD + upload) |
/rest/product/line | Line | Product lines (CRUD + upload) |
/rest/product/promo | Promo | Promo groups (CRUD + upload) |
/rest/product/variation | Variation | Product-variation assignments (CRUD) |
/rest/product/variation-group | VariationGroup | Variation groups (CRUD) |
/rest/product/variation-value | VariationValue | Variation values (CRUD) |
/rest/product/review | Review | Product reviews (CRUD) |
/rest/product/related | Related | Related product assignments (CRUD) |
/rest/product/related-group | RelatedGroup | Related product groups (CRUD) |
/rest/product/bundle | Bundle | Product bundles (CRUD) |
/rest/product/bundle-display | BundleDisplay | Bundle display messages (CRUD) |
/rest/product/bundle-criteria | BundleCriteria | Bundle criteria (CRUD) |
/rest/product/bundle-criteria-reference | BundleCriteriaReference | Bundle criteria references (CRUD) |
/rest/product/bundle-builder-reference | BundleBuilderReference | Bundle builder references (CRUD) |
/rest/product/bundle-pricing | BundlePricing | Bundle pricing rules (CRUD) |
/rest/product/download | Download | Product downloads (CRUD + upload) |
/rest/product/badge | Badge | Product badges (CRUD + upload) |
/rest/product/barcode | Barcode | Product barcodes (CRUD) |
/rest/product/product-list | ProductList | Product lists (CRUD + upload) |
/rest/product/product-list-group | ProductListGroup | Product list groups (CRUD) |
/rest/product/product-list-product-lp | ProductListProductLp | Product list product assignments (CRUD) |
/rest/product/product-meta | ProductMeta | Product metadata (CRUD) |
/rest/product/shelfcode | Shelfcode | Shelfcodes (CRUD) |
/rest/product/wishlist | Wishlist | Customer wishlists (CRUD) |
/rest/product/waiting-list | WaitingList | Stock waiting lists (CRUD) |
/rest/product/customization-schema | CustomizationSchema | Cart customization schemas (CRUD) |
/rest/product/price-tracking | PriceTracking | Price history (read-only) |
Admin Routes (Legacy Layer)
| Route | Controller Method | Description |
|---|---|---|
products_admin | index($offset) | Paginated product listing with search/filter |
products_admin/add | add() | Create product form and handler |
products_admin/edit/{id} | edit($id) | Edit product form and handler |
products_admin/cloneProduct/{id} | cloneProduct($id) | Clone existing product |
products_admin/delete/{id} | delete($id) | Soft-delete product |
products_admin/revert_deleted/{id} | revert_deleted($id) | Restore soft-deleted product |
products_admin/batch_action | batch_action() | Multi-product batch operations |
products_admin/product_prices_csv | product_prices_csv() | Update prices via CSV upload (barcode, wholesale, price) |
products_admin/import_products_from_excel | import_products_from_excel() | Import/update products from Excel file |
products_admin/new_products_list | new_products_list() | ERP import queue listing |
products_admin/add_new_product/{id} | add_new_product($id) | Add product from ERP import queue |
products_admin/delete_new_product/{id} | delete_new_product($id) | Delete ERP import queue entry |
products_admin/new_products_batch_action | new_products_batch_action() | Batch delete import queue entries |
products_admin/cloneNewProduct | cloneNewProduct() | Clone product from new products queue |
Admin menu entry under the PRODUCTS group. Requires roles: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, or AUTH_ROLE_PRODUCTS.
Code Flow
Create Product (addProduct(), line 266)
- Validation:
validation()sets form rules -- category required, at least one SKU, VAT required, vendor required, price required, name per language required, slug unique per language. - Upload downloads:
uploadProductDownloads()processes file uploads viaadvuploader. - Master data assembly:
getProductMasterPostData()extracts from POST:vat_id,shelfcode_id,price,point_factor,wholesale_price,acquisition_value,discount_persent,weight,vendor_id,product_view,active,product_img_type(badge),skroutz_name,negative_stock,pieces,cart_limit,hs_code,date_changed, customization schema fields, and conditionally special discount fields (special_from,special_to,special_discount_percent,disable_special_discounts). - MUI data assembly:
getAddProductDataMuiPost()extracts per language:name,description,short_description,ingredients,usage,extra_description,video,slug(auto-generated from name). SEO fields (meta_title,meta_keywords,meta_description) and URL fields (url,redirect_url) only for ADVISABLE role. - Before hook:
beforeAddEntityRecord($data, $dataMui)-- identity by default, overridable in client repos. - Insert:
product_model->add_record($data, $dataMui)-- inserts master row, setsold_id = id, inserts MUI rows per language. - Set relationships:
setNewEntityRelations($productId)handles category LP, feed LP, images, andsetGlobalEntityRelations(). - Email notification: If
EMAIL.NOTIFICATION_NEW_PRODUCTregistry is enabled, sends notification viaadv_mailer->inform_new_product(). - After hook:
afterAddProduct($id).
Update Product (edit(), line 768)
Same as create with these differences:
- Calls
update_record($data, $dataMui, $id)instead ofadd_record(). - Calls
setUpdateEntityRelations($id)instead ofsetNewEntityRelations(). - Slug preservation: existing slug preserved unless user has ADVISABLE role (who can manually set slugs).
- MUI update uses
updateOrInsertMui()(upsert pattern). beforeEditEntityRecord($id, $data, $dataMui)hook before update.afterEdit($id)hook after update.
Clone Product (cloneProduct(), line 711)
- Requires
enableCloneProductconfig flag. - Loads existing product data as template.
- On submit, runs the same flow as create (
add_record()+setNewEntityRelations()). - Fires
afterCloneProduct($id)hook (notafterAddProduct()). - Redirects to edit page of the newly created product.
Soft Delete (delete(), line 900)
- Calls
product_model->delete($id)which setssoft_delete = 1,active = 0,date_changed = now(). - Removes category associations from
shop_product_category_lp. - Fires
afterDelete($id)hook.
Restore (revert_deleted(), line 926)
Sets soft_delete = 0, active = 0 (product remains inactive until manually re-activated). Fires afterRevertDeleted($id) hook.
Entity Relationships Set on Save
setGlobalEntityRelations() (line 2186) runs for both create and update:
- Attributes:
manageProductAttributes($productId)->processProductCodes()-- syncs product codes (SKUs) and their attribute assignments. - Variations:
manageProductVariation($productId)->processProductVariation()-- assigns product to variation group with variation values. - Barcodes:
product_model->update_barcodes()-- syncs barcodes for the product. - Tags:
manageProductTags($productId)->product_tag_lp_model->update()-- syncs product-to-tag associations. - Downloads:
manageProductDownloads()-- handles uploaded download files, removes deleted ones. - Related products:
saveRelatedProducts($productId)-- saves per-group related product associations. - Product lines:
manageProductLines($productId)-- syncs line associations viashop_line_products. - Metadata:
updateProductMetaDataFromPost($productId)+saveProductMetaDataFiles()-- saves key-value metadata and file-type metadata.
Additional relationships handled by setNewEntityRelations() / setUpdateEntityRelations():
- Categories: Updates
shop_product_category_lpjunction table. - Feeds:
product_model->updateFeedsLp()-- syncsshop_product_feed_lpwith optional price modifiers. - Images/Media:
manageProductImages()-- processes JSON image data viaproduct_media_model->updateMediaRelations().
Batch Actions (batch_action(), line 1068)
30+ batch operations on selected products. Two-phase process: batchActionSelect() prepares the form, batchActionSubmit() executes.
Auto-return batch actions (execute immediately without confirmation form):
| Action | Effect |
|---|---|
set_active / set_inactive | Toggle active flag |
disable_special_discounts | Set disable_special_discounts = true, clear special discount fields |
enable_special_discounts | Set disable_special_discounts = false |
set_{feed}_xml / unset_{feed}_xml | Add/remove products from specific XML feed |
set_all_xml / unset_all_xml | Add/remove products from all enabled XML feeds |
set_gift_requirements | Redirect to gifts admin with selected product IDs |
Form-based batch actions (show confirmation/input form):
| Action | Effect |
|---|---|
vat_value | Change VAT rate for all selected products |
alter_shelfcode | Change shelfcode assignment |
put_in_category | Add products to a category |
put_in_category_and_remove_other_categories | Move products to a single category |
change_products_category | Move products from source to destination category |
assign_products_to_variation_group | Assign to existing variation group |
assign_products_to_new_variation_group | Create new variation group and assign |
assign_customization_group_to_products | Assign customization schema with date range |
put_in_lines | Assign to product line |
put_in_promo / remove_from_promo | Add/remove from promo group |
put_in_coupon | Assign to coupon |
put_icon | Set badge/icon |
assign_products_to_video | Assign to video |
assign_product_to_product_list / remove_from_product_list | Add/remove from product list |
set_tags / delete_tag | Add/remove tags |
change_products_vendor | Change vendor |
set_weight | Set weight |
set_product_page_view | Set product page layout variant |
special_discounts | Set special discount with date range (respects per-product disable_special_discounts) |
negative_stock | Toggle negative stock flag |
alter_cart_limit | Set cart limit |
discount_percent-extra-value | Apply additive discount percentage |
change_price_with_rate | Change prices by percentage rate |
change_xml_price_with_rate | Change XML feed price modifier |
add_product_relations | Vue component for adding related products |
add_event | Assign to event |
gift_per_count | Create mass gift rules |
CSV / Excel Import
product_prices_csv(): Upload CSV withbarcode,wholesale_price,priceformat. Updates prices by matching barcode.import_products_from_excel(): Controlled byESHOP.IMPORT_PRODUCTS_FROM_EXCEL_ENABLEDregistry. Supports: update products by barcode, update by product code, insert new products, and bulk image upload via ZIP. Uses PhpSpreadsheet.- New products list: ERP-imported products appear in a queue (
new_products_model). Admin can review, add to catalogue (add_new_product()), or delete.
Domain Layer
Modern Domain (src/Domains/Product/)
The Product domain is the largest in the system with 35 sub-domains registered in a single container.php.
Core Product
| Class | Responsibility |
|---|---|
Product\Repository\Entity | Entity mapping for shop_product. Properties: id, shelfcode_id, vat_id, point_factor, price, wholesale_price, acquisition_value, discount_persent, show_discount_value, disable_special_discounts, special_discount_percent, special_from/to, available_from/until, vendor_code, vendor_id, product_img_type, active, weight, pieces, cart_limit, soft_delete, csv_updated, negative_stock, hits, cart_product_customization_schema_id, cart_product_customization_range_from/to, product_view, hs_code, date_changed. Implements FilterTranslation. |
Product\Repository\MuiEntity | Entity mapping for shop_product_mui. Properties: id, product_id, name, description, short_description, ingredients, usage, extra_description, meta_title, meta_keywords, meta_description, url, redirect_url, video, slug, lang. |
Product\Repository\Repository | Read repository. Table: shop_product. |
Product\Repository\MuiRepository | Read repository for translations. Table: shop_product_mui. |
Product\Repository\RepositoryConfigurator | Defines 13 relations (see Relations section below). |
Product\Repository\MuiRepositoryConfigurator | Empty relations (MUI has no nested relations). |
Product\Repository\WriteRepository | Write repository for master table. |
Product\Repository\MuiWriteRepository | Write repository for translations. insertForEntity(), replaceForEntity(), deleteForEntity(). |
Product\Service | Read service. Builds specifications from ListRequest -- filters, sorts (including FilterByTranslation and SortByTranslation for locale-aware queries), pagination, relations. Methods: all(), item(), get(). |
Product\WriteService | CRUD service. create() validates via Validator, inserts master + translations transactionally. update() uses excludeNull to only send changed fields. delete() removes translations first, then master. |
Product\WriteData | Value object for master write fields. 33 properties mapping camelCase input to snake_case DB columns. OpenAPI schema Product. |
Product\MuiWriteData | Value object for translation write fields. 14 properties. OpenAPI schema ProductTranslation. Required: lang, name, slug. |
Product\Validator | Validates create/update data. Currently validates translation lang is required. |
Product\ListRequest | Defines allowed filters, sorts, and relation sorts for query building. |
Product Relations (RepositoryConfigurator)
| Relation Name | Type | Target | FK / Junction Table |
|---|---|---|---|
vendor | BELONGS_TO | Vendor\Repository | vendor_id |
vat | BELONGS_TO | Order\Vat\Repository | vat_id |
shelfcode | BELONGS_TO | Shelfcode\Repository | shelfcode_id |
badge | BELONGS_TO | Badge\Repository | product_img_type |
productCodes | ONE_TO_MANY | ProductCode\Repository | product_id |
barcodes | ONE_TO_MANY | Barcode\Repository | product_id |
translations | ONE_TO_MANY | MuiRepository | product_id |
categories | MANY_TO_MANY | Category\Repository | shop_product_category_lp (product_id, category_id) |
tags | MANY_TO_MANY | Tag\Tag\Repository | shop_product_product_tags (product_id, tag_id) |
lines | MANY_TO_MANY | Line\Repository | shop_line_products (product_id, line_id) |
variationValues | MANY_TO_MANY | Variation\Value\Repository | product_variation_values (product_id, variation_id) |
videos | MANY_TO_MANY | Cms\Video\Repository | shop_product_video_lp (product_id, video_id) |
events | MANY_TO_MANY | Event\Event\Repository | shop_product_event_lp (product_id, event_id) |
articles | MANY_TO_MANY | Cms\Blog\Article\Repository | product_blog (product_id, blog_id) |
ProductCode Relations (ProductCode\RepositoryConfigurator)
| Relation Name | Type | Target | FK |
|---|---|---|---|
media | ONE_TO_MANY | Media\Repository | product_code_id |
product | BELONGS_TO | Product\Repository | product_id |
attributes | ONE_TO_MANY | ProductCodeAttribute\Repository | product_code_id |
All Sub-Domains
| Sub-Domain | Table | MUI | Write | Description |
|---|---|---|---|---|
Product | shop_product | shop_product_mui | Yes | Core product entity |
Category | shop_product_category | shop_product_category_mui | Yes | Product categories (tree hierarchy) |
ProductCode | product_codes | -- | No (read-only) | SKUs with stock and active flag |
ProductCodeAttribute | product_code_attributes | -- | Yes | SKU-to-attribute value assignment |
Media | product_media | -- | Yes | Images/videos per product code |
Vendor | shop_vendor | shop_vendor_mui | Yes | Brands/vendors |
Attribute\Group | attribute_groups | attribute_groups_mui | Yes | Attribute groups (e.g., "Color", "Size") |
Attribute\Attribute | attribute_values | attribute_values_mui | Yes | Attribute values (e.g., "Red", "XL") |
Tag\Category | shop_product_tag_categories | shop_product_tag_categories_mui | Yes | Tag categories (grouping) |
Tag\Tag | shop_product_tags | shop_product_tags_mui | Yes | Tags within categories |
Line | shop_line | shop_line_mui | Yes | Product lines (within vendor) |
Promo | shop_promo | shop_promo_mui | Yes | Promotional groups |
Variation | product_variations | -- | Yes | Product-to-variation-group assignment |
Variation\Group | variation_groups | variation_groups_mui | Yes | Variation groups (e.g., "Color + Size") |
Variation\Value | variation_values | variation_values_mui | Yes | Variation values (e.g., "Red / Large") |
Badge | shop_product_badges | shop_product_badges_mui | Yes | Product badges/icons |
Barcode | shop_product_barcodes | -- | Yes | EAN/UPC barcodes per product+code |
Related | shop_related_products | -- | Yes | Related product assignments |
Related\Group | related_groups | related_groups_mui | Yes | Related product group types |
ProductList | product_list | product_list_mui | Yes | Curated product lists |
ProductList\Group | product_list_group | -- | Yes | Product list groups |
ProductList\ProductLp | product_list_product_lp | -- | Yes | Product-to-list assignments |
ProductMeta | product_meta | -- | Yes | Key-value metadata (type, category, lang) |
Shelfcode | shop_product_shelfcodes | -- | Yes | Warehouse shelfcode grouping |
Review | shop_product_reviews | -- | Yes | Customer product reviews |
Download | product_downloads | -- | Yes | Downloadable files per product |
Bundle | shop_product_bundles | -- | Yes | Product bundle definitions |
Bundle\Display | shop_product_bundles_display | -- | Yes | Bundle UI messages (per lang) |
Bundle\Criteria | shop_product_bundles_criteria | -- | Yes | Bundle matching criteria |
Bundle\CriteriaReference | shop_product_bundles_criteria_references | -- | Yes | Criteria reference items (products/categories) |
Bundle\BuilderReference | shop_product_bundles_builder_references | -- | Yes | Bundle builder reference items |
Bundle\Pricing | shop_product_bundles_pricing | -- | Yes | Bundle pricing rules |
Wishlist | shop_wishlist | -- | Yes | Customer wishlists |
WaitingList | shop_waiting_list | -- | Yes | Stock notification waiting list |
CustomizationSchema | cart_product_customizations_schema | -- | Yes | Cart product customization schemas |
PriceTracking | price_tracking | -- | No (read-only) | Price change history |
Legacy Model (ecommercen/eshop/models/Adv_product_model.php)
Extends Adv_base_model. Primary model for admin CRUD operations. Defines 23 table name properties for all product-related tables.
Key methods:
| Method | Description |
|---|---|
get_master_record($id) | Fetch single master record by ID |
getProductAdmin($productId) | Fetch product with categories, tags, feeds, lines, barcodes, MUI, media -- used by edit form |
add_record($data, $dataMui) | Insert master + set old_id = id + insert MUI rows per language |
update_record($data, $dataMui, $id) | Update master + upsert MUI via updateOrInsertMui() |
delete($id) | Soft delete: soft_delete = 1, active = 0 + remove category LP |
undelete_record($id) | Restore: soft_delete = 0, active = 0 |
getProductCodes($productIds) | Fetch SKUs with attribute assignments (joined query) |
getProductCodeImages($productIds) | Fetch media per product code |
batchMasterUpdate($ids, $data) | Bulk update master records by ID list |
updateFeedsLp($productIds, $feedIds, $modifiers) | Sync feed assignments with price modifiers |
insertFeedsLp($productIds, $feedIds) | Batch add products to feeds |
removeFeedsLp($productIds, $feedIds) | Batch remove products from feeds |
update_barcodes($vendorCode, $productId, $codes) | Sync barcode records |
updatePriceOnBarcode($barcode, $price, $wholesale) | CSV import price update by barcode |
change_price_with_discount_value($ids, $rate) | Batch adjust prices by percentage |
change_xml_price_modifier($ids, $rate, $feedIds) | Batch adjust XML feed price modifiers |
updateProductsVat($ids, $vatId) | Batch update VAT |
setProductsVendor($ids, $vendorId) | Batch update vendor |
update_batch_icons($ids, $iconId) | Batch update badge/icon |
Supporting Models
| Model | Table(s) | Purpose |
|---|---|---|
Adv_product_media_model | product_media, product_codes | Media CRUD, insertMediaRelations(), updateMediaRelations(), per-product-code images |
Adv_product_category_model | shop_product_category, shop_product_category_lp | Category tree and product-category junction |
Adv_product_tag_lp_model | shop_product_product_tags | Product-to-tag assignments |
Adv_product_tags_model | shop_product_tags, shop_product_tags_mui | Tag CRUD and batch operations |
Adv_related_product_model | shop_related_products, related_groups | Related product group management |
Adv_lines_model | shop_line, shop_line_products | Product line management |
Adv_promo_model | shop_promo, shop_promo_products | Promo group management |
AdvProductDownloadsModel | product_downloads | Downloadable files per product |
AdvProductMetaModel | product_meta | Key-value metadata with file uploads |
Adv_product_list_model | product_list, product_list_product_lp | Curated product lists |
Adv_product_blog_model | product_blog | Product-to-blog article associations |
Adv_attribute_values_model | attribute_values, attribute_groups | Attribute value management |
Adv_attribute_groups_model | attribute_groups, attribute_groups_mui | Attribute group management |
Adv_new_products_model | ERP import table | New product queue from ERP |
Adv_product_reviews_model | shop_product_reviews | Customer review management |
Adv_shelfcodes_model | shop_product_shelfcodes | Shelfcode grouping |
Adv_vendors_model | shop_vendor, shop_vendor_mui | Vendor/brand management |
Adv_vats_model | shop_product_vats | VAT rate management |
Architecture
REST Controller (src/Rest/Product/Controllers/Product.php)
Extends HandlesRestfulActions with HandlesWriteActions trait. Constructor receives ReadService, resource/collection classes, ListRequest class, and WriteService.
Fully annotated with OpenAPI attributes for spec generation. Supports collection (index()), single item by ID (show()), filter-based single item (item()), and full CRUD (store(), update(), destroy()).
REST Resources
| Class | Schema Name | Description |
|---|---|---|
Resource | ProductResource | Full product entity with context-sensitive backend fields, 13 nested relations |
Collection | ProductCollection | Paginated product list |
MuiResource | ProductMuiResource | Translation fields |
MuiCollection | ProductMuiCollection | Translation list |
The Resource class conditionally includes backend-only fields when context->isBackend() is true, keeping storefront API responses lean.
DI Container
Domain registration (src/Domains/Product/container.php):
- All 35 sub-domains registered with Repository, RepositoryConfigurator, Service, and where applicable WriteRepository, MuiRepository, MuiWriteRepository, Validator, WriteService.
- MUI repositories using
NullRelationConfiguratorfor sub-domains without MUI relations. - Product MUI repository uses custom
MuiRepositoryConfigurator.
REST registration (src/Rest/Product/container.php):
- 36 controllers wired with corresponding Service, Resource, Collection, ListRequest, WriteService references.
- Upload-enabled controllers (Category, Vendor, Tag, TagCategory, Supplier, Line, Promo, Download, Badge, ProductList) additionally receive
UploadService.
Legacy Controller (ecommercen/eshop/controllers/Adv_products_admin.php)
Extends Admin_c (2802 lines). Uses ProductVariationsTrait and aiContentGenerationTrait. Loads 30 models in constructor. Provides DownloadsToProduct helper and BlockBuilder for builder block integration.
Default ordering: shop_product.date_changed DESC. Session-persisted pagination offset and limit.
Data Model
shop_product (Master)
| Column | Type | Description |
|---|---|---|
id | int PK AI | Product ID |
shelfcode_id | int FK nullable | Warehouse shelfcode group |
vat_id | int FK | VAT rate reference |
point_factor | float nullable | Loyalty points multiplier |
price | decimal nullable | Retail price |
wholesale_price | decimal nullable | Wholesale price |
acquisition_value | decimal nullable | Cost/acquisition value |
discount_persent | decimal | Default discount percentage |
show_discount_value | tinyint | Display discount value on storefront |
disable_special_discounts | tinyint | Opt-out from special discount campaigns |
special_discount_percent | decimal | Special/promotional discount percentage |
special_from | datetime nullable | Special price start date |
special_to | datetime nullable | Special price end date |
available_from | datetime nullable | Product availability window start |
available_until | datetime nullable | Product availability window end |
vendor_code | varchar nullable | Vendor-assigned product code |
vendor_id | int FK nullable | Vendor/brand reference |
product_img_type | int FK | Badge/icon type (references shop_product_badges.id) |
active | tinyint | Product visible on storefront |
skroutz_name | varchar nullable | Override name for Skroutz feed |
weight | int | Weight in grams |
pieces | int | Number of pieces per unit |
cart_limit | int | Maximum quantity in cart (0 = unlimited) |
soft_delete | tinyint | Soft-delete flag (1 = trashed) |
csv_updated | tinyint | Updated via CSV/Excel import |
old_id | int nullable | Legacy system ID (set to id on creation) |
old_image | varchar nullable | Legacy image filename |
old_img_proceed | tinyint | Legacy image migration flag |
img_problem | tinyint | Image processing error flag |
date_changed | datetime nullable | Last modification timestamp |
negative_stock | tinyint | Allow orders when stock is zero |
hits | int | Storefront view counter |
cart_product_customization_schema_id | int FK nullable | Cart customization schema |
cart_product_customization_range_from | datetime nullable | Customization active period start |
cart_product_customization_range_to | datetime nullable | Customization active period end |
product_view | int | Product page layout variant |
hs_code | varchar nullable | Harmonized System customs code |
shop_product_mui (Translations)
| Column | Type | Description |
|---|---|---|
id | int PK AI | MUI record ID |
product_id | int FK | Parent product ID |
lang | varchar | Language code (el, en, etc.) |
name | varchar | Product name |
description | text | Full HTML description |
short_description | text | Short description |
ingredients | text | Ingredients (pharmaceutical/cosmetic) |
usage | text | Usage instructions |
extra_description | text | Additional description tab |
meta_title | varchar | SEO title |
meta_keywords | varchar | SEO keywords |
meta_description | text | SEO meta description |
url | varchar | Custom URL path |
redirect_url | varchar | Redirect URL (301) |
video | varchar | Video URL or embed code |
slug | varchar | URL-friendly slug (auto-generated, unique per language) |
builder_block_id | int nullable | FK to builder_blocks.id for page builder content |
product_codes (SKUs)
| Column | Type | Description |
|---|---|---|
id | int PK AI | Product code ID |
product_id | int FK | Parent product |
product_code | varchar | SKU string |
stock | int | Current stock quantity |
soft_deleted | tinyint | Soft-delete flag |
active | tinyint | Active flag |
product_media (Media)
| Column | Type | Description |
|---|---|---|
id | int PK AI | Media record ID |
product_code_id | int FK | Parent product code |
uri | varchar nullable | Image path or embed URL |
is_main | tinyint nullable | Primary image flag |
ref_code | varchar nullable | External reference code |
priority | int | Sort order |
type | enum | Media type: image, youtube, tiktok, vimeo, instagram, facebook |
metadata | text nullable | JSON metadata (alt text, etc.) |
product_code_attributes (SKU Attribute Assignments)
| Column | Type | Description |
|---|---|---|
id | int PK AI | Record ID |
product_code_id | int FK | Product code reference |
attribute_value_id | int FK | Attribute value reference |
attribute_groups / attribute_groups_mui (Attribute Groups)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Group ID |
display_type | varchar | Rendering type (dropdown, radio, etc.) |
MUI: name, lang per group.
attribute_values / attribute_values_mui (Attribute Values)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Value ID |
attribute_group_id | int FK | Parent group |
display_value | varchar nullable | Display override (e.g., hex colour) |
active | tinyint | Active flag |
MUI: name, lang per value.
shop_product_barcodes (Barcodes)
| Column | Type | Description |
|---|---|---|
id | int PK AI | Barcode record ID |
product_id | int FK | Product reference |
product_code | varchar | SKU string |
barcode | varchar | EAN/UPC barcode |
shop_vendor / shop_vendor_mui (Vendors)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Vendor ID |
logo | varchar | Logo filename |
vendor_banner / vendor_medium_banner / vendor_small_banner | varchar nullable | Banner images |
is_promo | tinyint | Featured vendor flag |
is_exclusive | tinyint | Exclusive vendor flag |
order | int | Sort order |
supplier_id | int FK nullable | Parent supplier |
slider_id | int FK nullable | Associated slider |
shop_product_tags / shop_product_tags_mui (Tags)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Tag ID |
tag_cat_id | int FK | Parent tag category |
tag_image | varchar nullable | Tag image |
order | int | Sort order |
shop_product_tag_categories / shop_product_tag_categories_mui (Tag Categories)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Tag category ID |
tag_category_image | varchar nullable | Category image |
tag_category_behavior | int | Display behaviour |
tag_values_behavior | int | Value selection behaviour |
order | int | Sort order |
shop_line / shop_line_mui (Product Lines)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Line ID |
vendor_id | int FK | Parent vendor |
line_image / line_front_image | varchar | Line images |
is_promo | tinyint | Featured line flag |
order | int | Sort order |
shop_promo / shop_promo_mui (Promo Groups)
| Column (master) | Type | Description |
|---|---|---|
id | int PK AI | Promo ID |
promo_image / promo_small_banner | varchar | Promo images |
view_type | int | Display type |
header_color / text_color | varchar | Styling colours |
do_follow | tinyint | SEO follow flag |
Variation Tables
| Table | Key Columns | Description |
|---|---|---|
variation_groups | id, display_type, step, display_position, priority, force_display_all_values | Variation groups (e.g., "Colour + Size") |
variation_groups_mui | group_id, name, lang | Group translations |
variation_values | id, variation_group_id, display_value, priority | Values within a group |
variation_values_mui | variation_value_id, name, lang | Value translations |
product_variations | id, group_id, product_id, is_master, priority | Product-to-group assignment |
Bundle Tables
| Table | Key Columns | Description |
|---|---|---|
shop_product_bundles | id, internal_name, is_active, weight, bundle_behavior, pricing_strategy, last_modified | Bundle definitions |
shop_product_bundles_display | id, bundle_id, message_key, message_value, lang | Bundle display messages |
shop_product_bundles_criteria | id, bundle_id, internal_name, discount, weight, reference_type | Bundle matching criteria |
shop_product_bundles_criteria_references | id, reference_id, quantity, criterion_id | Criteria reference items |
shop_product_bundles_builder_references | id, bundle_id, reference_id, reference_type, quantity | Builder reference items |
shop_product_bundles_pricing | id, bundle_id, criterion_id, discount, match_quantity | Bundle pricing rules |
Auxiliary Tables
| Table | Key Columns | Description |
|---|---|---|
shop_product_badges / _mui | id, name, badge | Product badge/icon images |
shop_supplier / shop_supplier_mui | id, logo, order | Suppliers (parent of vendors) |
product_list / product_list_mui | id, image, small_banner, header_color, text_color, ord, group_id | Curated product lists |
product_list_group | id, name, slug | Product list groups |
product_meta | id, product_id, meta_type, category, value, lang | Key-value metadata |
shop_product_shelfcodes | id, code | Warehouse shelf codes |
shop_product_reviews | id, product_id, customer_id, star_points, nickname, review_date, active, content, lang | Customer reviews |
product_downloads | id, product_id, file_type, file | Downloadable files |
shop_wishlist | id, customer_id, product_id | Customer wishlists |
shop_waiting_list | id, email, lang, product_id, creation_date, waiting_status, email_sent_date | Stock notification queue |
cart_product_customizations_schema | id, name, is_active, schema | Cart customization JSON schemas |
price_tracking | id, product_id, price_date, price | Price change audit trail |
shop_product_vats | id, ... | VAT rate definitions |
shop_prices_view | (database view) | Computed final prices (used for price range filtering) |
Junction Tables
| Table | Columns | Purpose |
|---|---|---|
shop_product_category_lp | product_id, category_id | Product-to-category (many-to-many). Now has UNIQUE INDEX product_category (product_id, category_id) per migration 20260529120000_dedup_product_category_lp_unique. |
shop_product_product_tags | product_id, tag_id | Product-to-tag (many-to-many) |
shop_line_products | product_id, line_id | Product-to-line (many-to-many) |
shop_promo_products | product_id, promo_id | Product-to-promo (many-to-many) |
shop_product_feed_lp | product_id, feed_id, price_modifier | Product-to-XML-feed with price modifier |
product_variation_values | product_id, variation_id | Product-to-variation-value (many-to-many) |
shop_product_video_lp | product_id, video_id | Product-to-video (many-to-many) |
shop_product_event_lp | product_id, event_id | Product-to-event (many-to-many) |
product_blog | product_id, blog_id | Product-to-blog-article (many-to-many) |
product_list_product_lp | id, ord, product_list_id, product_id | Product-to-product-list with ordering |
shop_related_products | id, product_id, related_product_id, group_id | Related product pairs within groups |
related_groups / related_groups_mui | id, relation | Related product group types |
Relevant Migrations
| Migration | Description |
|---|---|
20240923141428_new_bridge_tables | Creates bridge/junction tables for product relationships |
20241002113650_xml_price_modifier | Adds price_modifier to shop_product_feed_lp |
20241030144405_update_product_images_to_product_media | Renames product images to product media, adds type and metadata columns |
20241031164345_add_product_block_id | Adds builder block ID to product MUI |
20250124104616_add_hs_code_to_products | Adds hs_code column to shop_product |
20260529120000_dedup_product_category_lp_unique | Deduplicates shop_product_category_lp and replaces the old non-unique product_category KEY with UNIQUE INDEX product_category (product_id, category_id). down() empty (forward-only). (#279) |
Configuration
| Config | Location | Default | Description |
|---|---|---|---|
enableCloneProduct | application/config/main.php | varies | Enables product cloning |
enableProductVariations | application/config/main.php | varies | Enables variation system |
ENABLE_SPECIAL_DISCOUNTS (as OTHER.ENABLE_SPECIAL_DISCOUNTS) | Registry | varies | Enables special discount fields on products |
POINT_SYSTEM.IS_ENABLED | Registry | varies | Enables point_factor field |
ESHOP.POINT_FACTOR_TYPE | Registry | varies | Point factor calculation mode |
ESHOP.DEFAULT_POINT_FACTOR | Registry | varies | Default point multiplier |
EMAIL.NOTIFICATION_NEW_PRODUCT | Registry | varies | Send email on product creation |
ESHOP.IMPORT_PRODUCTS_FROM_EXCEL_ENABLED | Registry | varies | Enable Excel import feature |
ESHOP.BLOG_PRODUCT_CATEGORY_ENABLED | Registry | varies | Enable product-blog associations |
Images are stored under files/ subdirectories via the storage abstraction layer (local/S3/SFTP). Product media (product_media.uri) references paths within the storage system.
Validation Rules
| Field | Rule | Notes |
|---|---|---|
category_ids[] | required | At least one category |
productcodes | required | At least one SKU |
vat_value | greater_than[0] | VAT selection mandatory |
vendor_value | required, numeric, >0 | Vendor mandatory |
price | required, numeric | Base price mandatory |
name_{lang} | required | Product name per language |
slug_{lang} | unique (per language/product) | Auto-generated from name |
SEO fields (url, meta_*) | Only if ADVISABLE role | Access-controlled via allowMetaTags |
REST API validation via Validator class currently enforces lang required on each translation entry. Master field validation in WriteData is minimal (relies on DB constraints).
Client Extension Points
The admin controller provides 30+ empty hook methods designed for client repo overrides:
Core Hooks
| Hook | Trigger |
|---|---|
beforeAddEntityRecord($data, $dataMui) | Before product creation (can modify data) |
beforeEditEntityRecord($id, $data, $dataMui) | Before product update (can modify data) |
afterAddProduct($id) | After successful product creation |
afterEdit($id) | After successful product update |
afterCloneProduct($id) | After successful product clone |
afterDelete($id) | After successful soft delete |
afterRevertDeleted($id) | After successful undelete |
afterAddRender() | After add form render setup |
afterEditRender($productId) | After edit form render setup |
Batch Action Hooks
| Hook | Trigger |
|---|---|
afterBatchActionSubmitAssignProductsToVideo($products, $videoIds) | After video assignment |
afterBatchActionSubmitAssignCustomizationGroupToProducts($products, $id, $from, $to) | After customization schema assignment |
afterBatchActionSubmitAssignProductToProductList($products, $prodListId) | After product list assignment |
afterBatchActionSubmitRemoveFromProductList($products, $prodListId) | After product list removal |
afterBatchActionSubmitSetTags($products, $tagIds) | After tag assignment |
afterBatchActionDeleteProductsFromTag($products, $tagId) | After tag removal |
afterBatchActionSubmitSetWeight($products, $weight) | After weight change |
afterBatchActionSubmitSetProductPageView($products, $productView) | After page view change |
afterBatchActionSubmitChangeProductsVendor($products, $vendorId) | After vendor change |
afterBatchActionSubmitSpecialDiscounts($products, $updateData) | After special discount change |
afterBatchActionSubmitUpdateCategories($products, $categoryIds) | After category assignment |
afterBatchActionSubmitRemoveChangeProductCategory($products, $destinationCategoryId) | After category replacement |
afterBatchActionSubmitChangeProductCategory($products, $sourceCategoryId, $destinationCategoryId) | After category move |
afterBatchActionSubmitUpdateLine($products, $lineId) | After line assignment |
afterBatchActionSubmitUpdatePromo($products, $promoId) | After promo assignment |
afterBatchActionSubmitRemovePromo($products, $promoId) | After promo removal |
afterBatchActionSubmitUpdateCoupon($products, $couponId) | After coupon assignment |
afterBatchActionSubmitUpdateIcon($products, $iconId) | After badge change |
afterBatchActionSubmitGiftPlusOne($products, $giftData) | After gift rule creation |
afterBatchActionSubmitVatValue($products, $vatValue) | After VAT change |
afterBatchActionSubmitAlterShelfCode($products, $data) | After shelfcode change |
afterBatchActionSubmitChangeRow($products, $data) | Generic batch update fallback |
afterDeleteNewProduct($id) | After ERP import queue deletion |
afterNewProductsBatchActionDelete($productIds) | After batch queue deletion |
afterSetImportRecordRead($id, $addedProductId) | After ERP product adopted |
Client repos override the controller in application/controllers/ and implement these hooks for custom logic (e.g., Solr reindex, ERP sync, external API calls, cache invalidation).
The REST layer can be extended via Custom\Domains\Product\ and Custom\Rest\Product\ with DI alias overrides in custom/Domains/container.php and custom/Rest/container.php.
Business Rules
- Slug auto-generated -- From product name via
createSlug(), unique per language. Only ADVISABLE role can manually edit slugs. (ecommercen/eshop/controllers/Adv_products_admin.php,getAddProductDataMuiPost()) - SEO fields restricted --
meta_title,meta_keywords,meta_description,url,redirect_urleditable only by users withAUTH_ROLE_ADVISABLE. (ecommercen/eshop/controllers/Adv_products_admin.php,allowMetaTagscheck) - Soft delete, not hard delete --
soft_delete = 1, active = 0. Category LP removed. Reversible viarevert_deleted()(restores as inactive). (ecommercen/eshop/models/Adv_product_model.php,delete()/undelete_record()) - Media at product-code level -- Images/videos attached to
product_codesviaproduct_media, not directly toshop_product. Supports 6 types: image, youtube, tiktok, vimeo, instagram, facebook. (ecommercen/eshop/models/Adv_product_media_model.php) - JSON media upload -- Frontend sends JSON image data, processed via
product_media_model->updateMediaRelations(). Media includes metadata, priority, and main flag. Media files stored via SY-28 Storage Abstraction. - Email notification on create -- If
EMAIL.NOTIFICATION_NEW_PRODUCTregistry enabled, sends notification email via SY-24 Email Dispatch. (ecommercen/eshop/controllers/Adv_products_admin.php, line ~293) - Special discounts optional -- Controlled by
OTHER.ENABLE_SPECIAL_DISCOUNTSregistry. Per-product opt-out viadisable_special_discounts. Date-ranged (special_from/special_to). (ecommercen/eshop/controllers/Adv_products_admin.php,getProductMasterPostData()) - Point system optional --
POINT_SYSTEM.IS_ENABLEDcontrolspoint_factorfield visibility and processing. (ecommercen/eshop/controllers/Adv_products_admin.php,getProductMasterPostData()) - Stock via product codes -- Stock tracked per SKU (
product_codes.stock), not on master product.negative_stockflag allows ordering at zero stock. (ecommercen/eshop/models/Adv_product_model.php,getProductCodes()) - Availability window --
available_from/available_untilrestrict product visibility on storefront. (shop_productcolumns) - Cart limit --
cart_limitrestricts maximum quantity per cart (0 = unlimited). (shop_product.cart_limit) - Price tracking -- Price changes recorded in
price_trackingtable for audit/compliance. See SY-04 Price Tracking. (src/Domains/Product/PriceTracking/WriteService.php) - Customization schemas -- Products can reference a JSON schema for cart customization with active date range. (
cart_product_customizations_schematable) - HS codes -- Harmonized System customs codes for international shipping compliance. (
shop_product.hs_code) - Clone preserves data -- Product cloning copies all data and relationships but creates a new entity. (
ecommercen/eshop/controllers/Adv_products_admin.php,cloneProduct()) - Batch respects per-product flags -- Special discount batch operations skip products with
disable_special_discounts = true. (ecommercen/eshop/controllers/Adv_products_admin.php,batchActionSubmit()) old_idself-reference -- On creation,old_idis set to the newidfor legacy system compatibility. (ecommercen/eshop/models/Adv_base_model.php,add_record())- REST context-sensitive output -- Backend-only fields (
wholesalePrice,acquisitionValue, etc.) excluded from storefront API responses.negativeStockandpointFactorare exceptions: both are exposed to all contexts (public, customer, and backend).negativeStockallows headless consumers to determine true availability when stock is zero;pointFactoris assigned unconditionally in the base data array before theisBackend()guard and is not context-gated. (src/Rest/Product/Resources/Product/Resource.php,context->isBackend()) - Variation custom translation loading --
Variation\Repository\Repositoryuses customOneToManyloader that resolves translations viagroup_idinstead ofid. (src/Domains/Product/Variation/Repository/Repository.php) - Image storage via abstraction layer -- Product media files are stored and retrieved through the storage abstraction layer (local/S3/SFTP). See SY-28 Storage Abstraction and the Storage Guide.
- Product-category links are unique --
(product_id, category_id)pairs inshop_product_category_lpare DB-enforced unique since #279 (4.103). All write paths are idempotent throughlinkProductCategory()inAdv_product_category_model. (database/migrations/20260529120000_dedup_product_category_lp_unique)
Known Issues & Security Gaps
No known issues at the time of writing.
Related Flows
Customer Flows
- CF-01 Product Browsing -- product listing filtered by categories, tags, vendors
- CF-02 Product Detail -- frontend display of product data
- CF-04 Search -- product search results and filtering
- CF-05 Cart Management -- cart interactions with product stock and cart limits
- CF-12 Promotions Front -- storefront display of promo groups
- CF-14 Gift Rules -- gift product assignment in cart
- CF-15 Waiting List -- stock notification waiting list
- CF-16 Product Reviews -- customer review submission and display
- CF-17 Wishlist -- customer product wishlists
- CF-18 Product Tags -- tag-based filtering on storefront
- CF-19 Vendors -- vendor/brand pages and listing
- CF-24 Product Bundles -- bundle display and cart behaviour
- CF-25 Product Variations -- variation assignment and storefront display
- CF-36 Cart Customizations -- cart product customization schemas
Admin Flows
- AD-05 Category Management -- category assignment and hierarchy
- AD-07 Promotion Management -- promo groups and special discounts
- AD-08 Coupon Management -- coupon assignment to products via batch
- AD-09 Gift Rules -- gift rule configuration using product selections
- AD-12 Blog Admin -- product-blog article associations
- AD-15 Attributes & Tags -- attribute group/value and tag management
- AD-16 Variations Admin -- variation group and value management
- AD-17 Lines Admin -- product line management
- AD-19 Vendor Management -- vendor/brand CRUD
- AD-21 Slider Management -- vendor/category slider assignments
- AD-24 Bundles Admin -- product bundle configuration
- AD-30 Video Management -- video assignment to products
- AD-38 Product Relations -- related product group management
- AD-50 VAT Management -- VAT rate CRUD
- AD-51 Shelf Codes -- warehouse shelf code assignment per product
Integration Flows
- IN-01 Feed Generation -- XML feed assignment with price modifiers
- IN-08 ERP Integrations -- ERP product import queue
System Flows
- SY-04 Price Tracking -- price change history recording
- SY-06 Solr Indexing -- product search index updates
- SY-13 Waiting List Notifications -- stock availability email notifications
- SY-17 Bulk Product Import -- CSV/Excel import jobs
- SY-18 Image Upload ZIP -- bulk image import from ZIP
- SY-23 MUI Translation Pattern --
shop_product_muicompanion table stores per-language names, slugs, descriptions, SEO fields, and media - SY-24 Email Dispatch -- email sending for new product notifications
- SY-25 File Upload & Storage --
AdvUploaderandHandlesUploadActionshandle product image uploads; download file uploads handled via the upload pattern - SY-28 Storage Abstraction -- file storage for media and downloads
Wiki Guides: Storage Guide | REST API Modules Guide | Migrations Guide