Skip to content

Product Detail Page

Flow ID: CF-02 | Module(s): eshop, variations, Product domain | Complexity: High Last Updated: 2026-05-12

Business Overview

The product detail page (PDP) is the primary conversion page. It displays complete product information — pricing with discount logic, image galleries, variation matrices (size/color switchers), customer reviews, related products, and bundle offers — driving the "Add to Cart" action.

What customers experience:

  • Full product details: name, description, pricing (original vs. discounted), VAT
  • Image gallery with per-SKU images
  • Variation switcher (e.g., click "Red" → loads different product with red images/price)
  • Star rating average + individual reviews
  • Related products (fallback chain: curated list → product line → category → vendor)
  • Bundle offers, AI recommendations, blog articles
  • "Add to Cart" with variant/quantity selection

Key business behaviors:

  • Price calculation: final_price = price × (1 - discount%) × (1 + VAT%)
  • Special discounts override regular discounts when active (special_from/to date range)
  • Stock = sum of all active product code stocks; negative_stock flag allows pre-orders
  • cart_limit field caps per-product quantity
  • Hit counter incremented on every view
  • Canonical URL format: /vendors/{vendor_slug}/{product_slug}

API Reference

REST Endpoints

MethodPathAuthDescription
GET/rest/product/productGuestList products (filterable, sortable, paginated)
GET/rest/product/product/{id}GuestGet product by ID with relations
GET/rest/product/product/itemGuestGet single product by filter
POST/rest/product/productBackend (Products)Create product
POST/rest/product/product/{id}Backend (Products)Update product
DELETE/rest/product/product/{id}Backend (Products)Delete product

Filters: id, vatId, vendorId, badgeId, vendorCode, active, softDelete, negativeStock, name.{locale}, slug.{locale}, barcode
Sorts: id, price, hits, dateChanged, name.{locale}, slug.{locale}
Relations: translations, vendor, vat, shelfcode, badge, productCodes, barcodes, categories, tags, lines, variationValues, videos, events, articles

Browse endpoints interactively in the API Reference.

Legacy Storefront

URLControllerMethod
/{category-slug}/{product-slug}Adv_eshop.phpAdv_products.phpindex($catSlug, $slug)
/vendors/{vendor-slug}/{product-slug}sameCanonical format

Code Flow

Step 1: URL Resolution

File: Adv_eshop.php::baseProduct() (line 57-81)

Splits URL at last /. Checks product exists via existsBySlug() (cached, slug_lang index). Falls back to findSlugsByUrl() for old URL redirects.

Step 2: Product Data Loading

File: Adv_products.php::index() (line 45-217)

product_model->get_mui_record() joins 8 tables: shop_productshop_product_muishop_product_vatsshop_vendorshop_vendor_muishop_lineshop_line_mui. Returns 43+ fields.

Step 3: Price Calculation

File: Adv_product_parser_model.php::setPrices() (line 464-479)

original_price = price × (1 + VAT%)
If discount: final_price = price × (1 - discount%) × (1 + VAT%)
save_price = original_price - final_price

Special discount: if ENABLE_SPECIAL_DISCOUNTS AND now between special_from/to, uses special_discount_percent instead.

Step 4: Variations & SKUs

File: AdvVariationModel.php::getVariationByProductId() (line 19-66)

Loads variation matrix. If product in a variation group, getVariationGroupProducts() loads ALL sibling products with live data for frontend switcher.

File: Adv_related_product_model.php::getRelatedProductsFront() (line 85-103)

Fallback chain: product list → product line → category → vendor. All filtered by: active=1, soft_delete=0, stock>0, price>0.

Step 6: View Assembly

indexExtras() (line 222) orchestrates: reviews, hit tracking, last-seen, related products, Criteo/Agora/Manago integrations, AI recommendations, bundle offers, blog articles.


Domain & REST Architecture

ComponentPath
Servicesrc/Domains/Product/Product/Service.php
Repositorysrc/Domains/Product/Product/Repository/Repository.php
Entitysrc/Domains/Product/Product/Repository/Entity.php
MUI Repositorysrc/Domains/Product/Product/Repository/MuiRepository.php
WriteServicesrc/Domains/Product/Product/WriteService.php
Configuratorsrc/Domains/Product/Product/Repository/RepositoryConfigurator.php

Not yet migrated to Domain: Adv_product_parser_model (pricing), Adv_related_product_model (relations), AdvVariationModel (variations).


Client Extension Points

Controller Hooks

HookPurpose
indexExtras() (line 222)Add reviews, recommendations, tracking, bundles

Model Overrides

ModelMethods
Adv_product_parser_modelsetPrices(), setAvailabilityStock(), setDiscountDisclosure()
Adv_related_product_modelgetRelatedProductsFront() and individual fallback methods
AdvVariationModelgetVariationByProductId(), getVariationGroupProducts()

Configuration

KeyPurpose
ENABLE_SPECIAL_DISCOUNTSTime-limited special discount feature
OTHER.RELATED_PRODUCTS_LIMITMax related products displayed
CRITEO.IS_ENABLEDCriteo tracking
AGORA.DSPL_PRODUCT_IS_ENABLEDProduct page ads
MANAGO.ENABLE_RECOMMENDATIONSManago product suggestions

Data Model

For the full shop_product column schema (40+ columns), see AD-02 Product Management.

shop_product_mui — Product translations

ColumnTypeDescription
product_idINTFK to shop_product
langVARCHAR(2)Language code
nameVARCHAR(255)Translated product name
slugVARCHAR(255)URL slug (indexed with lang as slug_lang)
descriptionTEXT NULLFull product description (HTML)
short_descriptionTEXT NULLSummary description
meta_titleVARCHAR(255) NULLSEO title
meta_descriptionTEXT NULLSEO description

For the full product_codes column schema, see AD-02 Product Management.

Other tables involved

TablePurpose
shop_product_vatsVAT rates (vat_percent)
shop_vendor / shop_vendor_muiVendor data and translations
product_variations / product_variation_valuesVariation matrix (size, color groups)
product_mediaImages and videos per product/product code
shop_product_reviewsCustomer reviews (rating, text, approved flag)
shop_product_related_group_lpRelated product links (curated groups)
shop_line / shop_line_muiProduct lines (for related product fallback)

Vue Frontend Features

Product Price Chart

Renders a historical price evolution chart on the product detail page using ApexCharts. The feature is part of the SY-04 price tracking system and is gated by registry settings (productChartSettings.enabled, productChartSettings.enabledGraphs or enabledAllGraphs). The PHP view conditionally renders the component when $priceTrackingOptions->enabledGraphOnProductPage is true.

What customers see: A "Price Evolution" button (line-chart icon) near the product price area. Clicking it opens a modal containing a smooth area chart of historical prices over the configured tracking period, with dates on the X-axis and prices on the Y-axis. The chart highlights the most recent data point with an orange marker.

Component architecture:

ComponentPathPurpose
ProductPriceChartModalassets/main/vue/product/ProductPriceChartModal.vueButton + GenericModal wrapper; controls visibility via shouldDisplay computed and Vuex settings
ProductPriceChartassets/main/vue/product/ProductPriceChart.vueApexCharts area chart renderer; fetches and processes price data
productChartModuleassets/vue/store/productChart/productChartModule.jsNamespaced Vuex module storing per-product chart state (loading, options, series, prices, referencePrice)

Data flow:

  1. ProductPriceChartModal checks productChartSettings from Vuex (initialized from window.advAppData.productChartSettings) to decide whether to render.
  2. On button click, openModal() opens the GenericModal and emits updateChart to the child.
  3. ProductPriceChart.updateChart() first checks if data was already fetched (deduplication). If initial data exists in the Vuex products array (server-rendered priceTracking.prices), it uses that directly via setFromInitialData().
  4. Otherwise, it fetches from GET /rest/product/price-tracking/graph/{productId} (guest-accessible REST endpoint routed to Advisable\Rest\Product\Controllers\PriceTracking::graph(), which delegates to Advisable\Domains\Product\PriceTracking\GraphService::productPrices()).
  5. The response contains prices (array of {date, price}) and referencePrice. These are stored in the Vuex module keyed by product ID and rendered as an ApexCharts area series.

Price tracking backend:

  • Advisable\Domains\Product\PriceTracking\GraphService (src/Domains/Product/PriceTracking/GraphService.php) loads price changes from the price_tracking Repository, limits them to the configured tracking period, builds a daily price timeline, and determines referencePrice, saveValue, and discountPass per EU Omnibus rules.
  • Advisable\Domains\Product\PriceTracking\Options (src/Domains/Product/PriceTracking/Options.php) reads registry settings for the tracking period length, enabled status, and per-page graph toggles. Resolved via di()->get(Options::class) — same public property shape as the legacy DTO so view templates are unchanged.

Integration with AdvProductCard: The AdvProductCard component also reads productChartSettings to determine discount display behavior. When price tracking is enabled and priceTracking.shouldDisplay and discountPass are true, the card shows the reference price and save amount from price tracking data instead of the simple old/new price comparison.

PHP view integration:

php
<!-- application/views/main/layouts/product_page/product.php -->
<?php if ($priceTrackingOptions->enabledGraphOnProductPage) : ?>
    <product-price-chart-modal :product-id="<?= $productData->product_id; ?>"></product-price-chart-modal>
<?php endif; ?>

Video Reels (Stream Slider)

Displays short-form video reels with linked products, similar to social media story/reel formats. Videos are hosted on Bunny CDN and played via HLS streaming. The feature appears on the homepage as a horizontal slider and has a dedicated /reels page with pagination.

What customers see: A horizontally scrollable row of video thumbnail cards with titles. Clicking a thumbnail opens a fullscreen modal with the video playing on the left and a list of featured products (with images, prices, and "Add to Cart" buttons) on the right. Users can navigate between videos using prev/next buttons, mouse wheel scrolling, or touch swipe gestures. On mobile, the product list appears as a horizontal slider overlaying the bottom of the video.

Component architecture:

ComponentPathPurpose
HomeStreamSliderassets/main/vue/streamSlider/HomeStreamSlider.vueHomepage slider variant; reads data from window.advAppContext.advAppData.homeStreamSliderData; uses NativeSlider for horizontal scrolling
Reelsassets/main/vue/streamSlider/Reels.vueDedicated /reels page variant with server-side pagination; fetches pages via GET /api/reels?page={n}&perPage=12
StreamSlideassets/main/vue/streamSlider/StreamSlide.vueIndividual thumbnail card; shows video thumbnail or animated preview; handles hover/click/intersection events
StreamModalassets/main/vue/streamSlider/StreamModal.vueFullscreen playback modal; two-column layout (video + products on desktop); prev/next navigation; mute toggle; touch swipe support
VideoPlayerassets/main/vue/streamSlider/VideoPlayer.vueVideo.js wrapper supporting HLS (application/x-mpegURL), MP4, and DASH; hover-preview mode; autoplay with mute
StreamSliderMixinassets/main/vue/streamSlider/StreamSliderMixin.jsShared mixin providing v-intersect directive, slide navigation, scroll handling, view tracking (postHit), and i18n

Data flow:

  1. Server-side: Adv_home (homepage) or Adv_reels (reels page) loads videos from video_showcase_model, decorates them with Bunny CDN URLs via VideoManager, and enriches linked products with live pricing, stock, and image data.
  2. Video data is injected into the Vue app via window.advAppContext.advAppData (either homeStreamSliderData or streamSliderData).
  3. Each video object contains: id, name, cdn_video_id, playListUrl (HLS), playUrl, video_thumbnail, preview (animated), and a products array with id, name, url, image, priceWithSign.
  4. StreamSlide uses IntersectionObserver to preload preview animations as slides scroll into view.
  5. When a slide is clicked, StreamModal opens fullscreen, initializes VideoPlayer with the HLS playlist URL, and renders the product list with AdvButtonViewBuy components for add-to-cart functionality.
  6. View tracking: StreamSliderMixin.postHit() sends a POST to /video_showcase_admin/record_view with the video ID whenever playback starts.

Backend:

ComponentPathPurpose
Adv_reelsecommercen/eshop/controllers/Adv_reels.phpReels page controller; paginated video listing with product enrichment
AdvVideoShowcaseAdminecommercen/video/controllers/AdvVideoShowcaseAdmin.phpAdmin CRUD for video showcase entries
video_showcase_model(loaded via CI)Database queries for videos and linked products
VideoManager / BunnyStreamsrc/VideoStream/CDN URL resolution for playback, thumbnails, and previews

Routes:

URLControllerMethod
/reelsAdv_reelsindex() — server-rendered page
/api/reelsAdv_reelsgetPaginatedVideos() — JSON pagination API

Registry configuration:

KeyPurpose
VIDEOSHOWCASE.ENABLEDMaster toggle for the video showcase feature
VIDEOSHOWCASE.PROVIDERVideo CDN provider (currently BUNNY)

PHP view integration:

php
<!-- application/views/main/components/sliders/stream_slider.php -->
<home-stream-slider :show-add-to-cart="true" :see-more="false"></home-stream-slider>

Quick View Modal

Provides a popup product card that lets customers view product details, select attributes/variations, and add to cart without leaving the current listing page. The modal is rendered once globally in the main layout and shared across all pages.

What customers see: On product listing pages, clicking the eye icon (quick view button) or the "Add to Cart" button on a product with attributes/customizations opens a floating modal overlay. The modal shows the product image, name, description, product code, barcode, pricing (with discount and savings), attribute/variation selectors, quantity spinner, and an "Add to Cart" button. Clicking the close icon or adding to cart dismisses the modal.

Component architecture:

ComponentPathPurpose
AdvProductCardassets/main/vue/AdvProductCard.vueThe modal card itself; full mini-PDP with image, pricing, attributes, variations, customizations, and cart actions
AdvButtonViewBuyassets/main/vue/AdvButtonViewBuy.vueListing-page button that triggers the modal via openProductCardModal Vuex action; shortcuts to direct add-to-cart when product has only one code and no customizations

Vuex state (global store):

  • state.productCardModal{ open: boolean, productId: number|null } controls modal visibility
  • openProductCardModal(productId) mutation — sets productId and open = true
  • closeProductCardModal() mutation — resets to closed state
  • productCardModal getter — exposes state for the view template's v-show binding

Data flow:

  1. AdvButtonViewBuy is rendered per product in listing grids. When clicked, toggleQuickView() checks if the product has multiple codes or customizations. If so, it dispatches openProductCardModal(productId) to Vuex.
  2. The global <adv-product-card> in application/views/main.php is bound to productCardModal.productId and v-show="productCardModal.open". It renders in modal mode (:modal="true"), showing image, content, and name.
  3. AdvProductCard uses productMixin to load product data from the Vuex products array (server-rendered). It auto-selects the first product code if no attributes exist, and sets the main image per the selected product code.
  4. Price display integrates with price tracking: when productChartSettings.enabled is true and the product has valid tracking data (priceTracking.shouldDisplay and discountPass), it shows the reference price and save value from the tracking system. Otherwise, it falls back to standard old/new price display.
  5. The card supports AttributeSelect (product code attributes like size/color), VariationSelect (variation group switching), AdvCartProductCustomizationEditor (schema-based product customizations), and AdvProductInputSpinner (quantity with stock limits).
  6. On "Add to Cart", addToCard() dispatches addToCartClicked with the selected product code ID, quantity, and optional recommendation tracking. If customizations are active, they are validated and included in the payload.
  7. closeModal() emits close-quick-view, which the parent template handles via closeProductCardModal.

PHP view integration:

html
<!-- application/views/main.php (global, rendered once) -->
<adv-product-card
    :modal="true"
    :product-id="productCardModal.productId"
    :show-image="true"
    :show-content="true"
    :show-name="true"
    v-on:close-quick-view="closeProductCardModal"
    v-show="productCardModal.open">
</adv-product-card>

Key behaviors:

  • The modal is a singleton rendered once in the layout; the displayed product changes by updating productCardModal.productId in Vuex.
  • Stock-aware: disables add-to-cart when stock is zero or cart limit is reached; shows unavailability messages for inactive or out-of-stock product codes.
  • Availability date awareness: respects availableFrom / availableUntil fields to hide the cart button for time-restricted products.
  • Note: On the PDP itself, AdvProductCard is used inline (non-modal, :modal="false") as the add-to-cart widget with show-content, show-name, and show-image all set to false — only the attribute selectors and cart button are visible.

Known Issues & Security Gaps

  1. Price chart API endpoint updated (2026-05-12): The Vue component ProductPriceChart.vue previously called the legacy GET /api/priceTracking/productPrices/{productId} endpoint, which was deleted in PR #128. The component has been updated to call the new REST endpoint GET /rest/product/price-tracking/graph/{productId}. File: assets/main/vue/product/ProductPriceChart.vue:232.

No other known security gaps at time of writing.


Tests

The price-chart Vue component (ProductPriceChart.vue, ProductPriceChartModal.vue) and the productChartModule Vuex store do not have automated unit tests. The backend graph logic is covered by tests/Unit/Domains/Product/PriceTracking/GraphServiceTest.php (Omnibus helpers) and tests/Unit/Domains/Product/PriceTracking/PriceCalculatorTest.php (VAT + discount math).


Wiki Guides: Product queries use PSR-6 caching — see Cache System. Product images served via Storage Abstraction.

Shared Patterns