Skip to content
  1. Extras
  2. MiniShop3
  3. Development
  4. Events
  5. Cart

Cart events ​

Adding, changing and removing products in the cart.

Besides the parameters listed below, every event on this page also receives controller — the call wrapper adds it.

Which events can abort the operation ​

$modx->event->output(...) does not stop the work everywhere: the core reads the plugin response only for some of the events.

EventReaction to output()
Every msOnBefore*The operation is aborted, the client gets an error
msOnAddToCartThe product is already saved, but the request still returns an error
msOnChangeInCart, msOnChangeOptionInCart, msOnRemoveFromCart, msOnEmptyCartIgnored — the operation has already completed
msOnGetCart, msOnGetStatusCartUsed only to substitute data, not to abort. But a non-empty output() loses the substitution as well: the core keeps the original value

Put checks in the paired msOnBefore* events. Where the response is ignored, a plugin error disappears and the customer never sees it.

msOnBeforeGetCart ​

Fired before getting cart contents.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
draftmsOrderOrder draft (cart)

Aborting the operation ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeGetCart':
        /** @var \MiniShop3\Controllers\Cart\Cart $controller */
        $controller = $scriptProperties['controller'];
        $draft = $scriptProperties['draft'];

        // E.g. deny cart access for certain users
        if ($modx->user->get('id') == 123) {
            $modx->event->output('Access denied');
            return;
        }
        break;
}

msOnGetCart ​

Fired after getting cart contents. Lets you modify data before it is returned.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
draftmsOrderOrder draft (cart)
dataarrayCart products array

Modifying data ​

php
<?php
switch ($modx->event->name) {
    case 'msOnGetCart':
        $controller = $scriptProperties['controller'];
        $data = $scriptProperties['data'];

        // Add extra data to each product
        foreach ($data as $key => &$item) {
            $product = $modx->getObject(\MiniShop3\Model\msProduct::class, $item['product_id']);
            if ($product) {
                $item['sku'] = $product->get('article');
                $item['thumb'] = $product->get('thumb');
            }
        }

        // Return modified data
        $values = &$modx->event->returnedValues;
        $values['data'] = $data;
        break;
}

msOnBeforeAddToCart ​

Fired before adding a product to the cart. Lets you validate or modify add parameters.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
msProductmsProductProduct object
countintQuantity
optionsarrayProduct options

Aborting the operation ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeAddToCart':
        /** @var \MiniShop3\Model\msProduct $product */
        $product = $scriptProperties['msProduct'];
        $count = $scriptProperties['count'];

        // Disallow adding products with zero price
        if ($product->get('price') <= 0) {
            $modx->event->output('Product is not available for order');
            return;
        }

        // Disallow adding more than 10 units
        if ($count > 10) {
            $modx->event->output('Maximum 10 units per product');
            return;
        }
        break;
}

Modifying data ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeAddToCart':
        $product = $scriptProperties['msProduct'];
        $count = $scriptProperties['count'];
        $options = $scriptProperties['options'] ?? [];

        $values = &$modx->event->returnedValues;

        // Minimum quantity — 2
        if ($count < 2) {
            $values['count'] = 2;
        }

        // Add source tag to options
        $options['source'] = 'promo_landing';
        $values['options'] = $options;
        break;
}

msOnAddToCart ​

Fired after a product has been successfully added. The product is already saved and the draft is recalculated.

Can fail an already completed request

The product is saved by this point, yet the plugin can still call $modx->event->output(...) — add() then returns an error to the client although the cart has already changed.

Not fired when the same product is added again

If the product+options key is already in the cart, msOnBeforeAddToCart still fires. output() on it also blocks the quantity increase. Then add() hands the work to change(), msOnBeforeChangeInCart and msOnChangeInCart fire, and msOnAddToCart does not. Quantities are summed: 2 units added to the existing 3 give count = 5.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
msProductmsProductProduct object
countintQuantity added
optionsarrayProduct options
product_keystringUnique product key in cart

Example ​

php
<?php
switch ($modx->event->name) {
    case 'msOnAddToCart':
        $product = $scriptProperties['msProduct'];
        $count = $scriptProperties['count'];
        $productKey = $scriptProperties['product_key'];

        // Log add
        $modx->log(modX::LOG_LEVEL_INFO, sprintf(
            '[Cart] Product added: %s (ID: %d), qty: %d, key: %s',
            $product->get('pagetitle'),
            $product->get('id'),
            $count,
            $productKey
        ));

        // Send event to analytics
        // analytics()->track('add_to_cart', [...]);
        break;
}

msOnBeforeChangeInCart ​

Fired before changing product quantity in the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
product_keystringUnique product key in cart
countintNew quantity

Aborting the operation ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeChangeInCart':
        $productKey = $scriptProperties['product_key'];
        $count = $scriptProperties['count'];

        // Disallow changing certain products
        if (strpos($productKey, 'promo') !== false) {
            $modx->event->output('Promo product quantity cannot be changed');
            return;
        }
        break;
}

Modifying data ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeChangeInCart':
        $count = $scriptProperties['count'];

        // Cap maximum
        $values = &$modx->event->returnedValues;
        if ($count > 50) {
            $values['count'] = 50;
        }
        break;
}

msOnChangeInCart ​

Fired after changing product quantity in the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
product_keystringUnique product key
countintNew quantity

Example ​

php
<?php
switch ($modx->event->name) {
    case 'msOnChangeInCart':
        $productKey = $scriptProperties['product_key'];
        $count = $scriptProperties['count'];

        $modx->log(modX::LOG_LEVEL_INFO,
            "Quantity changed: {$productKey} => {$count}"
        );
        break;
}

msOnBeforeChangeOptionsInCart ​

Fired before changing product options in the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
product_keystringUnique product key
optionsarrayNew options

Aborting the operation ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeChangeOptionsInCart':
        $options = $scriptProperties['options'];

        // Disallow changing color to "gold" (exclusive)
        if (isset($options['color']) && $options['color'] === 'gold') {
            $modx->event->output('Gold color is not available');
            return;
        }
        break;
}

Modifying data ​

The options can be substituted — the core reads them back from the response if the value is an array:

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeChangeOptionsInCart':
        $options = $scriptProperties['options'];

        // Normalise the case of the values
        $options = array_map('mb_strtolower', $options);

        $values = &$modx->event->returnedValues;
        $values['options'] = $options;
        break;
}

The substituted options take part in computing product_key, so the cart row key changes along with them.


msOnChangeOptionInCart ​

Fired after changing product options in the cart.

Not fired when keys collide

If the new combination of options matches an existing cart row, the core removes the current row and hands the work over to change() — msOnBeforeChangeInCart/msOnChangeInCart fire instead of this event. The quantities of the two rows are added together.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
old_product_keystringOld product key
product_keystringNew product key (may change)
optionsarrayNew options

Example ​

php
<?php
switch ($modx->event->name) {
    case 'msOnChangeOptionInCart':
        $oldKey = $scriptProperties['old_product_key'];
        $newKey = $scriptProperties['product_key'];
        $options = $scriptProperties['options'];

        if ($oldKey !== $newKey) {
            $modx->log(modX::LOG_LEVEL_INFO,
                "Product key changed: {$oldKey} => {$newKey}"
            );
        }
        break;
}

msOnBeforeRemoveFromCart ​

Fired before removing a product from the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
product_keystringUnique product key

Aborting the operation ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeRemoveFromCart':
        $productKey = $scriptProperties['product_key'];

        // Disallow removing required product
        // (e.g. delivery service added automatically)
        if ($productKey === 'ms_delivery_service') {
            $modx->event->output('This product cannot be removed');
            return;
        }
        break;
}

msOnRemoveFromCart ​

Fired after removing a product from the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
product_keystringRemoved product key

Example ​

php
<?php
switch ($modx->event->name) {
    case 'msOnRemoveFromCart':
        $productKey = $scriptProperties['product_key'];

        $modx->log(modX::LOG_LEVEL_INFO,
            "Product removed from cart: {$productKey}"
        );

        // Analytics notification
        // analytics()->track('remove_from_cart', [...]);
        break;
}

msOnBeforeEmptyCart ​

Fired before fully clearing the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller

Aborting the operation ​

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeEmptyCart':
        // Disallow clearing cart at certain times
        $hour = (int)date('G');
        if ($hour >= 23 || $hour < 6) {
            $modx->event->output('Cart cannot be cleared at night');
            return;
        }
        break;
}

msOnEmptyCart ​

Fired after fully clearing the cart.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller

Example ​

php
<?php
switch ($modx->event->name) {
    case 'msOnEmptyCart':
        $modx->log(modX::LOG_LEVEL_INFO, 'Cart cleared');

        // A good place to drop your own cart-bound state. MiniShop3 core keeps
        // no such keys of its own — things like a promo code come from add-ons,
        // for example ms3PromoCode.
        unset($_SESSION['myshop']['promo']);
        break;
}

msOnGetStatusCart ​

Fired when calculating cart status (totals) — both on cart mutations and on every Cart::getStatus(), that is on cart/get, on cart/status and when the msCart snippet renders. Lets you modify the totals.

output() discards the status patch too

Both call sites read returnedValues['status'] only on a successful response. If a plugin writes its own status and calls output() at the same time, the patch is not applied: the original calculateStatus() result comes back and the message lands in the response as an error.

Parameters ​

ParameterTypeDescription
controller\MiniShop3\Controllers\Cart\CartCart controller
statusarrayCart totals array

status structure ​

php
$status = [
    'total_count' => 5,        // Total units across all rows
    'total_cost' => 15000,     // Total cost
    'total_weight' => 12.5,    // Total weight in ms3_weight_unit (kg by default)
    'total_discount' => 1500,  // Total discount
    'total_positions' => 3,    // Number of positions (cart rows)
];

There are no other keys in status. CartItemManager::calculateStatus() builds the whole array.

Modifying data ​

php
<?php
switch ($modx->event->name) {
    case 'msOnGetStatusCart':
        $status = $scriptProperties['status'];

        // Add bonus points
        $status['bonus_points'] = floor($status['total_cost'] / 100);

        // Add free delivery info
        $status['free_delivery'] = $status['total_cost'] >= 5000;
        $status['free_delivery_diff'] = max(0, 5000 - $status['total_cost']);

        $values = &$modx->event->returnedValues;
        $values['status'] = $status;
        break;
}

Outputting extra info ​

Once the status is modified, the data is available on the frontend:

fenom
{* In cart chunk *}
{if $status.free_delivery}
    <div class="free-delivery">Free delivery!</div>
{else}
    <div class="delivery-info">
        Until free delivery: {$status.free_delivery_diff | number_format : 0 : '' : ' '} ₽
    </div>
{/if}

{if $status.bonus_points > 0}
    <div class="bonus">You will get {$status.bonus_points} bonus points</div>
{/if}