
Product events (catalog)
Modifying product price, weight and fields on catalog output.
Feature
These events support a plugin chain — each plugin can read the previous result from $modx->eventData and pass its own result to the next one.
msOnGetProductPrice
Fired when getting product price. Lets you modify the price on the fly.
Parameters
| Parameter | Type | Description |
|---|---|---|
msProductData | msProductData | Product data object |
data | array | Extra product data |
price | float | Current price |
Modifying data (with chain support)
<?php
switch ($modx->event->name) {
case 'msOnGetProductPrice':
$price = $modx->eventData['msOnGetProductPrice']['price']
?? $scriptProperties['price'];
$data = $scriptProperties['data'];
if ($data['parent'] == 5) {
$price = $price * 0.85;
}
$modx->eventData['msOnGetProductPrice']['price'] = $price;
$values = &$modx->event->returnedValues;
$values['price'] = $price;
break;
}Personal prices
<?php
switch ($modx->event->name) {
case 'msOnGetProductPrice':
$price = $modx->eventData['msOnGetProductPrice']['price']
?? $scriptProperties['price'];
$productData = $scriptProperties['msProductData'];
if ($modx->user->isMember('VIP')) {
$price = $price * 0.8; // -20%
}
if ($modx->user->isMember('Wholesale')) {
$wholesalePrice = $productData->get('wholesale_price');
if ($wholesalePrice > 0) {
$price = $wholesalePrice;
}
}
$modx->eventData['msOnGetProductPrice']['price'] = $price;
$values = &$modx->event->returnedValues;
$values['price'] = $price;
break;
}Dynamic markup by currency rate
<?php
switch ($modx->event->name) {
case 'msOnGetProductPrice':
$price = $modx->eventData['msOnGetProductPrice']['price']
?? $scriptProperties['price'];
$rate = $modx->cacheManager->get('usd_rate') ?? 90;
$productData = $scriptProperties['msProductData'];
if ($productData->get('currency') === 'USD') {
$price = $price * $rate;
}
$modx->eventData['msOnGetProductPrice']['price'] = $price;
$values = &$modx->event->returnedValues;
$values['price'] = round($price, 2);
break;
}msOnGetProductWeight
Fired when getting product weight. Lets you modify weight on the fly.
Parameters
| Parameter | Type | Description |
|---|---|---|
msProductData | msProductData | Product data object |
data | array | Extra product data |
weight | float | Current weight |
Modifying data
<?php
switch ($modx->event->name) {
case 'msOnGetProductWeight':
$weight = $modx->eventData['msOnGetProductWeight']['weight']
?? $scriptProperties['weight'];
$data = $scriptProperties['data'];
$packagingWeight = 0.5;
$weight = $weight + $packagingWeight;
$modx->eventData['msOnGetProductWeight']['weight'] = $weight;
$values = &$modx->event->returnedValues;
$values['weight'] = $weight;
break;
}Volumetric weight
<?php
switch ($modx->event->name) {
case 'msOnGetProductWeight':
$weight = $modx->eventData['msOnGetProductWeight']['weight']
?? $scriptProperties['weight'];
$productData = $scriptProperties['msProductData'];
$length = $productData->get('length') ?? 0;
$width = $productData->get('width') ?? 0;
$height = $productData->get('height') ?? 0;
if ($length > 0 && $width > 0 && $height > 0) {
$volumeWeight = ($length * $width * $height) / 5000;
$weight = max($weight, $volumeWeight);
}
$modx->eventData['msOnGetProductWeight']['weight'] = $weight;
$values = &$modx->event->returnedValues;
$values['weight'] = $weight;
break;
}msOnGetProductFields
Fired when getting product fields. Lets you add or modify any fields.
Parameters
| Parameter | Type | Description |
|---|---|---|
msProductData | msProductData | Product data object |
data | array | Product fields array |
Modifying data
<?php
switch ($modx->event->name) {
case 'msOnGetProductFields':
$data = $modx->eventData['msOnGetProductFields']['data']
?? $scriptProperties['data'];
$data['discount_percent'] = 0;
if ($data['old_price'] > 0 && $data['price'] > 0) {
$data['discount_percent'] = round(
(($data['old_price'] - $data['price']) / $data['old_price']) * 100
);
}
$stock = $data['stock'] ?? 0;
if ($stock <= 0) {
$data['availability'] = 'out_of_stock';
$data['availability_text'] = 'Out of stock';
} elseif ($stock < 5) {
$data['availability'] = 'low_stock';
$data['availability_text'] = 'Low stock';
} else {
$data['availability'] = 'in_stock';
$data['availability_text'] = 'In stock';
}
$modx->eventData['msOnGetProductFields']['data'] = $data;
$values = &$modx->event->returnedValues;
$values['data'] = $data;
break;
}Adding related data
<?php
switch ($modx->event->name) {
case 'msOnGetProductFields':
$data = $modx->eventData['msOnGetProductFields']['data']
?? $scriptProperties['data'];
$productData = $scriptProperties['msProductData'];
$reviewCount = $modx->getCount('msProductReview', [
'product_id' => $data['id'],
'published' => 1,
]);
$data['review_count'] = $reviewCount;
$c = $modx->newQuery('msProductReview');
$c->where(['product_id' => $data['id'], 'published' => 1]);
$c->select('AVG(rating) as avg_rating');
if ($c->prepare() && $c->stmt->execute()) {
$data['avg_rating'] = round($c->stmt->fetchColumn(), 1);
}
$modx->eventData['msOnGetProductFields']['data'] = $data;
$values = &$modx->event->returnedValues;
$values['data'] = $data;
break;
}Full example: discounts and promotions
<?php
/**
* Plugin: Discounts and promotions
* Events: msOnGetProductPrice, msOnGetProductFields
*/
switch ($modx->event->name) {
case 'msOnGetProductPrice':
$price = $modx->eventData['msOnGetProductPrice']['price']
?? $scriptProperties['price'];
$data = $scriptProperties['data'];
$discounts = [];
$categoryDiscounts = [
5 => 10,
10 => 15,
];
if (isset($categoryDiscounts[$data['parent']])) {
$discount = $categoryDiscounts[$data['parent']];
$price = $price * (1 - $discount / 100);
$discounts['category'] = $discount;
}
if ($modx->user->isAuthenticated()) {
$price = $price * 0.95;
$discounts['member'] = 5;
}
if ($modx->user->isMember('VIP')) {
$price = $price * 0.9;
$discounts['vip'] = 10;
}
$modx->eventData['discounts'] = $discounts;
$modx->eventData['msOnGetProductPrice']['price'] = $price;
$values = &$modx->event->returnedValues;
$values['price'] = round($price, 2);
break;
case 'msOnGetProductFields':
$data = $modx->eventData['msOnGetProductFields']['data']
?? $scriptProperties['data'];
$discounts = $modx->eventData['discounts'] ?? [];
if (!empty($discounts)) {
$data['applied_discounts'] = $discounts;
$data['total_discount'] = array_sum($discounts);
}
$badges = [];
if ($data['new']) {
$badges[] = ['type' => 'new', 'text' => 'New'];
}
if ($data['popular']) {
$badges[] = ['type' => 'popular', 'text' => 'Bestseller'];
}
if (!empty($data['total_discount'])) {
$badges[] = [
'type' => 'sale',
'text' => '-' . $data['total_discount'] . '%',
];
}
$data['badges'] = $badges;
$modx->eventData['msOnGetProductFields']['data'] = $data;
$values = &$modx->event->returnedValues;
$values['data'] = $data;
break;
}msOnGetPublicSeo
Fired in PublicSeoService after the SEO set and the TV overlay map (ms3_public_seo_tv_map) are assembled, just before the final whitelist. Works for both products and categories. On list and tree requests include_seo=0 by default; on get it is 1.
Parameters
| Parameter | Type | Description |
|---|---|---|
seo | array | The current SEO whitelist |
payload | array | The catalog row (product or category) |
og_type | string | product, or website for a category |
Modifying
A plugin returns a patch in $modx->event->returnedValues['seo']. Unknown keys are dropped by the whitelist. og.title / og.description copy title / description unless the patch sets them explicitly in seo.og.
<?php
switch ($modx->event->name) {
case 'msOnGetPublicSeo':
$seo = $scriptProperties['seo'];
$payload = $scriptProperties['payload'];
$ogType = $scriptProperties['og_type'];
if ($ogType === 'product' && !empty($payload['vendor'])) {
$seo['description'] = trim(($seo['description'] ?? '') . ' — ' . $payload['vendor']);
}
$values = &$modx->event->returnedValues;
$values['seo'] = $seo;
break;
}