Skip to content

Gift Rules Management (Admin)

Flow ID: AD-09 Module(s): eshop, Promotion (modern domain) Complexity: Medium-High Last Updated: 2026-05-19

Business Context

Gift rules allow store administrators to configure promotional logic that automatically grants free products to customers when certain conditions are met during cart evaluation and checkout. The system supports 13 distinct rule types covering product-based, vendor-based, cart-total, and combination triggers. Each gift rule defines what products or vendors trigger it (requirements), what products the customer receives (choices), how many qualifying items are needed per gift (gift_per_count), and how many gifts the customer can pick (gift_user_choice_count). Rules are date-bounded, have optional stock limits, and can be flagged as promotional banners for storefront display.

Entry Points

TypePath / TriggerControllerMethod
Admin listing/gifts_adminAdv_gifts_adminindex()
Add gift rule/gifts_admin/addAdv_gifts_adminadd()
Edit gift rule/gifts_admin/edit/{id}Adv_gifts_adminedit($id)
Delete gift rule/gifts_admin/delete/{id}Adv_gifts_admindelete($id)
Toggle active/gifts_admin/setGiftActivity/{id}/{0|1}Adv_gifts_adminsetGiftActivity()
Product search/gifts_admin/search_append_item/{mode}Adv_gifts_adminsearch_append_item()
Mass gift creation/products_admin (batch action)Adv_products_adminbatch submit
REST API/rest/promotion/giftRest\Promotion\Controllers\GiftCRUD
REST Choices/rest/promotion/gift-choiceRest\Promotion\Controllers\GiftChoiceCRUD
REST Requirements/rest/promotion/gift-requirementRest\Promotion\Controllers\GiftRequirementCRUD
Admin menuMarketing groupapplication/config/admin_menu.phproute gifts_admin

Authorization

Roles: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_MARKETING

Enforced in the controller constructor via allowRole(). The admin menu entry mirrors the same three roles. REST API endpoints are protected under backend auth with ADMIN + MARKETING roles (configured in application/config/rest_policies.php).

Code Path Trace

Step 1: Listing (index)

File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- index($offset) (line 36)

  1. Search/filter handling (lines 38-63): Stores conditions in session (giftsSearch). Filters: active (live status), text (internal_name LIKE), giftRules (rule_id IN), maxStock (joins gift_choices and shop_product to find gifts whose gift products have stock <= N).
  2. Count and fetch (lines 65-74): gifts_model->countAdminList($conditions) for pagination, gifts_model->getAdminList($conditions, $limit, $offset) for paginated results.
  3. Active rule calculation (line 112-118): getActiveGiftRules() determines currently live rules (date range, active flag, remaining stock, valid choices). Results are ordered by live status first (via FIELD() SQL), then by date_end DESC.
  4. Data enrichment (lines 132-183): Each row gets its requirements and choices loaded via gift_requirements_model->getOptions() and gift_choices_model->getOptions(). Product and vendor names/details are resolved for display.
  5. Render: Passes giftRules dropdown (rule names), giftRulesHelp (rule descriptions), records with isLive flag.

View: application/views/admin/gifts/list.php

The listing table columns: ID (with green/red live indicator dot), Internal Name, Date Range, Active status, Prerequisites (collapsible panel showing rule type, amounts, vendors, products with stock badges), Remaining (infinity symbol if NULL), Gift Per Count, Gift Products (product names with stock), and Actions (toggle active, edit, delete with JS confirmation).

Search panel provides: text search on internal_name, multi-select rule type filter, active/inactive/indifferent dropdown, and max stock threshold filter.

Step 2: Create Gift Rule (add)

File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- add() (line 92)

  1. Validation (line 96): Calls validation() which sets CodeIgniter form validation rules.
  2. File uploads (lines 99-109): Handles per-language image uploads via advuploader->uploadFilesSameFolder() to files/gifts/.
  3. Rule cleanup (line 113): gift_rules_model->postDataRuleCleanupHelper($allPost) removes fields inapplicable to the selected rule type (see Rule Type Field Mapping below) and forces defaults.
  4. Collect associations (lines 115-135):
    • dom_req_ID[] -- requirement product IDs (option_type = 1)
    • req_vendor_ids[] -- requirement vendor IDs (option_type = 2)
    • dom_gift_ID[] -- gift choice product IDs (option_type = 1)
  5. Build gift data (lines 137-150): rule_id, amount_from, amount_to, internal_name, date_start, date_end, active, gift_per_count, gift_user_choice_count, is_promo, priority, remaining (NULL = unlimited).
  6. MUI data (lines 153-167): Per language: description, url, image, promo_image, extra_vendor_image, extra_product_image.
  7. Persist (line 176): gifts_model->addGift() inserts master, captions, requirements, and choices in a single transaction.
  8. Hook: afterAdd($giftId) -- empty in base, overridable in client repos.

View: application/views/admin/gifts/create.php

Form structure:

  • MUI tab section: Description (text input per language), Badge Image (file per language)
  • Promo section: is_promo checkbox, Promo Image (file per language), Extra Vendor Image, Extra Product Image, Priority (integer), URL (per language)
  • Rule selection: Dropdown of all 13 rule types with help text panel
  • Amount range: amount_from / amount_to (numeric, step 0.01) -- shown/hidden per rule type
  • Requirement products: AJAX product search with inline results (search_append_item('req'))
  • Requirement vendors: Multi-select dropdown from vendors combo
  • Gift products: AJAX product search (search_append_item('gift'))
  • Gift user choice count: How many gifts the customer can pick (0 = automatic)
  • Date range: Datetime pickers for start/end
  • Active: Checkbox
  • Internal name: Admin-only label (required in HTML, trim-only in server validation)
  • Gift per count: Number 1-10, how many qualifying items needed per gift
  • Remaining: Stock counter (empty = unlimited NULL)

Step 3: Edit Gift Rule (edit)

File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- edit($id) (line 211)

Same flow as create with these differences:

  • Loads existing gift via gifts_model->getGift($id) with captions, requirements, and choices
  • Image handling preserves existing images unless a new file is uploaded or the del_* checkbox is checked (e.g., del_image, del_promo_image, del_extra_vendor_image, del_extra_product_image)
  • Calls gifts_model->updateGift() which updates master, captions, and diff-updates requirements/choices (adds new, removes deleted)
  • Hook: afterEdit($giftId) -- overridable

Step 4: Delete Gift Rule

File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- delete($id) (line 350)

Deletes: requirements (gift_requirements), choices (gift_choices), captions (gifts_mui), master (gifts). No soft-delete -- hard removal. No referential integrity check against past orders (gifts in orders are stored separately).

Hook: afterDelete($giftId) -- overridable

Step 5: Toggle Active Status

File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- setGiftActivity($giftId, $active) (line 366)

Direct update of active flag on the gifts table. Does not affect date range or remaining stock. Redirect back to listing.

File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- search_append_item($mode) (line 377)

  • Ajax-only, POST queryString
  • Searches shop_product by: name LIKE, vendor_code exact, product_id exact, product_code exact (via product_codes subquery)
  • Returns active, non-deleted products with name, vendor info, stock, discount, images
  • Limited to 40 results, ordered by name ASC then stock DESC
  • Cached for 15 seconds via pscache
  • $mode parameter (req or gift) determines the JS behavior for adding to requirements vs. gift choices

Step 7: Mass Gift Creation from Product Admin

File: ecommercen/eshop/controllers/Adv_products_admin.php (line 1557-1569)

Batch action on the product listing page. When gift_per_count is submitted, calls gifts_model->addMassGiftsForNoPlusOne($products, $postData). Creates one gift per selected product using Rule 1 (products required), where each product is both the requirement AND the gift choice (buy X, get X free). Internal names auto-incremented as "{name} - 1", "{name} - 2", etc.

The 13 Rule Types

Rule types are defined as a private instance array in Adv_gift_rules_model::$data (not a database table). Each rule entry specifies:

PropertyPurpose
idNumeric rule identifier
nameTranslation key for admin display
helpTranslation key for help tooltip
validatorMethod name on Adv_gifts_model called during cart evaluation
removeFieldsPOST fields stripped on save (fields not applicable to this rule type)
setDefaultsField values forced on save (e.g., gift_per_count = 1 for rules 5, 7)

Rule Type Reference

IDNameTrigger ConditionRemoved FieldsForced Defaults
1Products requiredSpecific products in cartamount_from, amount_to, req_vendor_ids--
2Vendors requiredProducts from specific vendors in cartamount_from, amount_to, dom_req_ID--
3Cart totalCart total within amount rangereq_vendor_ids, dom_req_ID--
4Products + cart totalSpecific products AND cart total in rangereq_vendor_ids--
5Products + min total amountRequired products' total value in rangereq_vendor_idsgift_per_count = 1
6Vendors + cart totalVendor products AND cart total in rangedom_req_ID--
7Vendors + min total amountVendor products' total value in rangedom_req_IDgift_per_count = 1
8Vendors + min item priceIndividual vendor product prices in rangedom_req_ID--
9Products + min item priceIndividual required product prices in rangereq_vendor_ids--
10Products (combination)ALL specified products must be in cartamount_from, amount_to, req_vendor_ids--
11Products required (one gift)Specific products in cart; caps at gift_per_countamount_from, amount_to, req_vendor_ids--
12Vendors required (one gift)Vendor products in cart; caps at gift_per_countamount_from, amount_to, dom_req_ID--
13Products required -- cheapest freeSpecific products in cart; cheapest becomes freeamount_from, amount_to, req_vendor_ids, dom_gift_IDgift_user_choice_count = 0

Rule Type Field Mapping

The postDataRuleCleanupHelper() method (line 192) strips inapplicable POST fields per rule type before saving:

  • Product-based rules (1, 10, 11): Remove amount_from, amount_to, req_vendor_ids -- no amount range or vendor conditions needed
  • Rule 13: Remove amount_from, amount_to, req_vendor_ids, dom_gift_ID -- additionally strips dom_gift_ID because Rule 13 never reads gift_choices at runtime (gift pool is computed dynamically) (Adv_gift_rules_model.php:133)
  • Vendor-based rules (2, 12): Remove amount_from, amount_to, dom_req_ID -- no amount range or product conditions needed
  • Cart total rule (3): Remove req_vendor_ids, dom_req_ID -- only amount range matters
  • Hybrid rules (4, 5, 9): Remove req_vendor_ids -- products + amount, no vendor conditions
  • Hybrid rules (6, 7, 8): Remove dom_req_ID -- vendors + amount, no product conditions

Rule 13 Special Behavior

Rule 13 ("Products required -- get cheapest free") is unique:

  • No entries in gift_choices table -- the gift pool is computed dynamically at cart evaluation time
  • gift_user_choice_count forced to 0 on save (no customer selection presented)
  • getCheapestRequirementProductInCart() identifies the lowest-priced qualifying product in the cart by final_price; on tie, lowest product ID wins (deterministic)
  • The free product's quantity is subtracted from the paid cart row in baseParseCartContents
  • getActiveGiftRules() explicitly skips the gift_choices existence check for rule 13 so these gifts appear as "live"
  • Admin order page: Vue Vuex getter forces ruleSatisfiesChoiceSelection = true for rule 13
  • Save-side guard (4.101.0): postDataRuleCleanupHelper() strips dom_gift_ID before save (Adv_gift_rules_model.php:133), so Rule 13 saves can no longer create gift_choices rows

Validation Rules

FieldRuleNotes
rule_idrequired, integerMust match one of the 13 defined rules
gift_per_countrequired, numeric, 1-10How many qualifying items needed per gift
gift_user_choice_countrequired, numeric, >= 00 = automatic assignment (rule 13 always forced to 0)
remainingtrim onlyNULL = unlimited stock; numeric value decremented on order
amount_fromnumericLower bound (inclusive) of amount range; 0 if not applicable
amount_tonumericUpper bound (exclusive); 0 means no upper limit
internal_nametrimAdmin-only label; HTML required attribute on form
date_startrequiredDatetime for rule activation
date_endrequiredDatetime for rule expiry
activetrimCheckbox flag (0/1)
description_{lang}trim (MUI)Per-language description

Gift Quantity Calculation Logic

When a gift rule is evaluated at cart time:

validatorResult = validator(...)           // How many qualifying units
giftCountToGive = floor(validatorResult / gift_per_count)  // How many gifts earned
if remaining is not null:
    giftCountToGive = min(giftCountToGive, remaining)      // Stock cap
if giftCountToGive <= 0:
    skip rule

The remaining counter is decremented on order creation via gifts_model->updateCounter($giftId, $subtractCount). If remaining is NULL, no limit is applied and no decrement occurs.

Promo Display Feature

Gifts flagged with is_promo = 1 are displayed as promotional banners on the storefront homepage. The Adv_front_controller::getActiveGiftRules() method (line 1336) splits active gift rules into two groups:

  • activeGiftRules['promo'] -- rules with is_promo = 1, rendered as promotional banners with promo_image, extra_vendor_image, extra_product_image, URL, and priority ordering
  • activeGiftRules['normal'] -- standard gift rules

This is loaded on the homepage via Adv_home::index() and provides marketing visibility for active gift promotions. The priority field controls display ordering (higher = first).

Data Model

Database Tables

TablePurposeKey Columns
giftsGift rule masterid, rule_id, internal_name, gift_per_count, gift_user_choice_count, amount_from (decimal 9,2), amount_to (decimal 9,2), date_start, date_end, active, is_promo, remaining (NULL = unlimited), priority
gifts_muiMulti-language contentgift_id, lang, description, image (badge), promo_image, extra_vendor_image, extra_product_image, url
gift_requirementsTrigger conditionsgift_id, option_type (1 = product, 2 = vendor), option_type_id
gift_choicesGift products offeredgift_id, option_type (1 = product), option_type_id (product ID)

One-Time Data Migration (4.101.0)

Migration database/migrations/20260511120000_cleanup_rule13_gift_choices.php and patcher patches/CleanupRule13GiftChoices.php purged historical orphan gift_choices rows for rule_id = 13 (DELETE via JOIN) introduced before dom_gift_ID was added to removeFields. These rows were written by the admin form but never read at runtime.

Indexes (gifts table)

date_start, date_end, date_period (composite), active, active_date_start, active_date_end, active_date_period, is_promo, active_period_promo_remaining (composite covering the default active query).

Entity Relationships

gifts (1) ──< gifts_mui (N)          -- per language
gifts (1) ──< gift_requirements (N)  -- products/vendors that trigger
gifts (1) ──< gift_choices (N)       -- products offered as gifts
gift_requirements.option_type_id ──> shop_product.id (when option_type=1)
gift_requirements.option_type_id ──> shop_vendor.id  (when option_type=2)
gift_choices.option_type_id      ──> shop_product.id (when option_type=1)

Model Layer

Legacy Models

ModelFilePurpose
Adv_gifts_modelecommercen/eshop/models/Adv_gifts_model.phpCore gift CRUD and all 13 validation strategies
Adv_gift_rules_modelecommercen/eshop/models/Adv_gift_rules_model.phpStatic rule type registry (in-memory, not DB)
Adv_gift_options_modelecommercen/eshop/models/Adv_gift_options_model.phpBase class for option tables (diff-based update)
Adv_gift_requirements_modelecommercen/eshop/models/Adv_gift_requirements_model.phpExtends Gift_options_model, table gift_requirements
Adv_gift_choices_modelecommercen/eshop/models/Adv_gift_choices_model.phpExtends Gift_options_model, table gift_choices; adds getOptionsWithActiveStockForGiftIds() to filter out-of-stock gift products

The Adv_gift_options_model uses a diff-based update strategy: on save, it compares current DB entries with the new set, deletes removed IDs and inserts new ones (never doing a full wipe-and-reinsert).

Modern Domain Layer (PSR-4)

ComponentPathPurpose
Gift Entitysrc/Domains/Promotion/Gift/Repository/Entity.phpRead entity with all gifts columns
Gift MUI Entitysrc/Domains/Promotion/Gift/Repository/MuiEntity.phpgifts_mui columns
Gift Repositorysrc/Domains/Promotion/Gift/Repository/Repository.phpSpecification-based queries
Gift RepositoryConfiguratorsrc/Domains/Promotion/Gift/Repository/RepositoryConfigurator.phpRelations: translations (ONE_TO_MANY MUI), choices (ONE_TO_MANY GiftChoice), requirements (ONE_TO_MANY GiftRequirement)
Gift Servicesrc/Domains/Promotion/Gift/Service.phpRead service
Gift WriteServicesrc/Domains/Promotion/Gift/WriteService.phpCreate/update/delete with MUI
Gift WriteDatasrc/Domains/Promotion/Gift/WriteData.phpDTO: ruleId, internalName, giftPerCount, giftUserChoiceCount, amountFrom, amountTo, dateStart, dateEnd, active, isPromo, remaining, priority
Gift MuiWriteDatasrc/Domains/Promotion/Gift/MuiWriteData.phpDTO: lang, description, image, promoImage, extraVendorImage, extraProductImage, url
Gift Validatorsrc/Domains/Promotion/Gift/Validator.phpCreate/update validation (currently placeholder)
GiftChoice Entity/Reposrc/Domains/Promotion/GiftChoice/CRUD for gift_choices table
GiftRequirement Entity/Reposrc/Domains/Promotion/GiftRequirement/CRUD for gift_requirements table
DI Containersrc/Domains/Promotion/container.phpRegisters all Promotion domain services

REST API

Full CRUD exposed via modern REST controllers. Auth: backend JWT with ADMIN or MARKETING role.

EndpointMethodControllerAction
/rest/promotion/giftGETGiftList with filters, sorts, pagination
/rest/promotion/gift/{id}GETGiftSingle record
/rest/promotion/gift/itemGETGiftSingle by filter
/rest/promotion/giftPOSTGiftCreate
/rest/promotion/gift/{id}POSTGiftUpdate
/rest/promotion/gift/{id}DELETEGiftDelete
/rest/promotion/gift-choiceGET/POST/DELETEGiftChoiceCRUD for gift choices
/rest/promotion/gift-requirementGET/POST/DELETEGiftRequirementCRUD for gift requirements

Filters: id (exact), ruleId (exact), active (exact), isPromo (exact), internalName (partial), description.{locale} (partial).

Relations (via ?with=): translations, choices, requirements.

Sorts: id, ruleId, amountFrom, amountTo, dateStart, dateEnd, active, isPromo, priority, description.{locale}.

Admin Views

ViewFilePurpose
Listapplication/views/admin/gifts/list.phpPaginated table with search/filter panel
Createapplication/views/admin/gifts/create.phpFull form with MUI tabs, AJAX product search
Updateapplication/views/admin/gifts/update.phpSame as create with pre-populated values and image deletion
AJAX search resultsapplication/views/admin/gifts/ajax_search_list.phpProduct search dropdown results
JS item templateapplication/views/admin/gifts/js_add_item.phpClient-side template for adding products to requirement/gift lists

The create/update views feature embedded Scribe How tutorials (scribe.how.marketing.gift.rules and scribe.how.marketing.gift.actions) for admin user guidance.

Admin Order Integration

Gift rules are evaluated on the admin order create/edit page via a dedicated Vue component:

File: assets/admin/js/order-gifts-block.js

The Vuex store manages: giftRulesWithProducts, selectedGiftsWeights, selectedInGroups, orderSelectedGiftIds. The getter getGiftRulesWithProducts enriches each rule with ruleRequiresChoiceSelection and ruleSatisfiesChoiceSelection flags. Rule 13 auto-satisfies choice selection (no admin interaction needed).

The admin AJAX endpoints gifts_for_products and validateProductGiftSelections in Adv_orders_admin.php evaluate gift eligibility for the current order's product set, returning available rules with choices. Rule 13 validation is skipped (auto-assigned). As of 4.101.0 (commit bd187f2db), both Adv_orders_admin.php:1513 and validateProductGiftSelections() at Adv_orders_admin.php:2624 delegate to $this->gifts_model->filterApplicableGiftRules(), eliminating the prior admin-only divergence that dropped Rule 13 gifts from the validated set.

Tests

TestFileCoverage
Legacy validator unit teststests/Legacy/Eshop/AdvGiftsModelTest.phpruleProductsValidator, ruleProductsCombinationValidator, ruleTotalCartValidator, ruleProductsCheapestFreeValidator, getCheapestRequirementProductInCart (greedy allocation, tie-break, cart-qty cap), filterApplicableGiftRules Rule 13 exemption — all via reflection
Legacy order-model Rule 13 unit teststests/Legacy/Eshop/AdvOrderModelRule13Test.phpgetRule13GiftAdjustmentsForProducts, adjustCartForRule13Gifts (qty trim, full-row removal, code-mismatch fallback, exact-code-preference guard) via reflection; 13 tests, all passing after #204 fix
Legacy model DB integration (Rule 13)tests/Integration/Legacy/Eshop/AdvGiftsModelTest.phpgetActiveGiftRules Rule 13 bypass — confirms Rule 13 gift is retained when gift_choices is empty, while non-Rule-13 gifts with the same shape are filtered out
Integration: Gift Servicetests/Integration/Domains/Promotion/Gift/ServiceTest.phpModern domain read/write service
Integration: Gift Repositorytests/Integration/Domains/Promotion/Gift/RepositoryTest.phpRepository queries
Integration: GiftChoicetests/Integration/Domains/Promotion/GiftChoice/Choice CRUD
Integration: GiftRequirementtests/Integration/Domains/Promotion/GiftRequirement/Requirement CRUD

Business Rules Summary

RuleLocationDescription
Amount range semanticsruleTotalCartValidatoramount_from is inclusive (<=), amount_to is exclusive (<); amount_to = 0 means no upper limit
Rule 10 = combinationruleProductsCombinationValidatorALL required products must be in cart; returns minimum quantity across all (bottleneck product)
Rules 11, 12 = single gift capValidatorsEven with many qualifying products, returns at most gift_per_count gifts
Rule 13 = auto-assign cheapestgetCheapestRequirementProductInCartNo gift_choices; dynamic pool from live cart prices
Stock limit via remaininggetGiftForProductsInCart line 656NULL = unlimited; decremented on order creation
gift_per_count = trigger thresholdCalculation line 654floor(validatorResult / gift_per_count) determines number of gifts
Date range requiredAdmin validationdate_start <= NOW < date_end for rule to be live
File uploads to files/gifts/Controller add/edit4 image fields per language: image, promo_image, extra_vendor_image, extra_product_image
Deletion is harddeleteGift()Removes all related rows (requirements, choices, MUI, master)
Gift stock filteringgetOptionsWithActiveStockForGiftIds()Gift choice products with availabilityStock <= 0 are excluded
Mass creationaddMassGiftsForNoPlusOne()Creates Rule 1 gifts where each product is both requirement and gift (buy X get X)

Client Customization Points

Override TypeBase FileOverride InWhat Can Change
Gifts modelecommercen/eshop/models/Adv_gifts_model.phpapplication/modules/eshop/models/Validator logic, new rule types, custom conditions
Gift rules modelecommercen/eshop/models/Adv_gift_rules_model.phpapplication/modules/eshop/models/Rule definitions, field visibility per type
Gift admin controllerecommercen/eshop/controllers/Adv_gifts_admin.phpapplication/modules/eshop/controllers/Admin form, validation rules, afterAdd/afterEdit/afterDelete hooks
Gift viewsapplication/views/admin/gifts/Same path in client repoForm layout, additional fields
Modern domainsrc/Domains/Promotion/Gift/custom/Domains/Promotion/Gift/ with DI aliasService logic, validation

Known Issues & Security Gaps

  • [RESOLVED 4.101.0, commit 879423e21] Rule 13 admin form persisted dom_gift_ID to gift_choices — the field was written but never read at runtime. Fixed by adding dom_gift_ID to Adv_gift_rules_model.php:133 removeFields. One-time cleanup migration purged existing orphan rows.

  • [RESOLVED 4.101.0, commit bd187f2db] validateProductGiftSelections() (Adv_orders_admin.php:2624) used an inline array_filter that lacked the Rule 13 carve-out, causing Rule 13 gifts to be dropped from admin order validation. Resolved by delegating to the canonical Adv_gifts_model::filterApplicableGiftRules() method which carries the Rule 13 exemption at line 524.

  • [OPEN] Admin form still renders the gift-products picker (dom_gift_ID input) when Rule 13 is selected. The input is silently discarded by postDataRuleCleanupHelper() on save — admins can configure gift choices that vanish on submit. Sibling rules 6/7/8 have the same shape for dom_req_ID. Tracked separately (referenced in commit 879423e21 body).

  • [OPEN -- minor] Dangling duplicate $productReqOptionType = 1; at Adv_orders_admin.php:2626 — harmless (used by the subsequent foreach block) but a cosmetic cleanup candidate left over from the inline closure removal.

Wiki Guides

  • Gifts Module Guide -- detailed walkthrough of gift rule configuration and the 13 rule types