Adapt Your Add-ons and Themes to CS-Cart 4.21.1

Core Changes

Changes in TyghToolsMath class

The brick/math package (v0.10.2) was added to the core composer.json.

The floorToPrecision() and ceilToPrecision() methods were refactored to use Brick\Math\BigDecimal instead of native PHP floating-point arithmetic, fixing precision errors when rounding numbers to a given step (e.g. prices to 0.01, filter ranges to 0.1). If BigDecimal operations fail, both methods fall back to the previous native PHP implementation. Method signatures remain unchanged, but the returned values may differ in edge cases (e.g. 299.99 with precision 0.01, 11.225 with precision 0.01).

If your add-on duplicates price rounding logic using the sprintf() with round((double) $price + 0.00000000001, $decimals) workaround, consider replacing it with Tygh\Tools\Math::roundToPrecisionString().

Upcoming PHP version requirements

We plan to raise the minimal supported PHP version in the releases that come after 4.21.1:

  • PHP 7 support will be dropped completely. The new minimal supported version is expected to be PHP 8.0 (the version we actually need to raise the system requirements to).
  • However, we recommend switching to PHP 8.3 right now. It is the latest version currently supported by CS-Cart (support was added in 4.19.1).
  • Along with dropping PHP 7, we will add support for newer PHP versions, including PHP 8.5.
  • We expect that many store owners will want to upgrade to PHP 8.5 once a CS-Cart version with PHP 8.5 support is released. Please take this into account in your development and deployment plans.

Optional New Interface for Shipping Services with Pickup Point Selection

A new interface Tygh\Shippings\IPickupPointsService has been introduced. You can implement it in your shipping service if you want to normalize pickup point data into the unified format defined by the Tygh\Shippings\PickupPoints\Dto\PickupPoint class.

Previously, shipping services with pickup point selection had to implement the Tygh\Shippings\IPickupService interface. As changing its methods would break existing integrations, the new interface has been introduced alongside the old one.

We did this to standardize pickup point data and to:

  1. Avoid separate templates and controllers for each shipping service.
  2. Store pickup points in a shared cache table instead of keeping them in the session, as some services previously did (for example, Store Pickup). This significantly improves the performance of pages that use pickup point lists, especially the checkout page.
  3. Potentially display pickup points from all shipping services in a single list and on a single map in the future.

For these reasons, the new Tygh\Shippings\IPickupPointsService interface was introduced, while the old interface remains available for backward compatibility.

How does the new interface work?

  1. Your shipping service must implement the getPickupPointsLoader(): IPickupPointsLoaderByLocation method, which should return a loader object implementing the Tygh\Shippings\PickupPoints\LoaderByLocation\IPickupPointsLoaderByLocation interface.
  2. In turn, the IPickupPointsLoaderByLocation requires your loader class to implement the getPickupPointsByLocation(): PickupPoint[] method. This method must return a list of pickup points, where each pickup point is represented by the Tygh\Shippings\PickupPoints\Dto\PickupPoint object.
  3. Next, the core automatically detects whether or not your shipping service implements the new interface. Depending on the result, it chooses either the new or the legacy logic for retrieving pickup points.

By implementing a method that returns pickup points in a standardized format, you enable the core to use shared templates for displaying and working with the pickup point list. For example, if a unified map displaying pickup points for all available shipping methods is introduced in the future, your pickup points will automatically be included.

What does the PickupPoint object contain?

The Tygh\Shippings\PickupPoints\Dto\PickupPoint class contains the basic fields for pickup point information required for display, such as the city, address, coordinates, business hours, and similar properties. To store all additional data returned by the shipping provider’s API, the class also includes a dedicated $shipping_extra field, to which all the data should be passed as an array.

How is the pickup point list cached?

Pickup points are cached in the shipping_pickup_points table. Its columns correspond to the fields of the PickupPoint class. At the moment, each shipping service is responsible for implementing its own caching logic within its loader class.

Functions to work with the cache tables are available in Tygh\Shippings\PickupPoints\Repository.

  1. Before requesting pickup points from the provider’s API, cache_key is generated from the request parameters, which is hash('sha256', implode(',', $params)). Before generating the hash, the params must be sorted.
  2. Use findFreshPickupPointsSyncStates($provider, $cache_key) to check whether fresh pickup points already exist. Pickup points are considered fresh if they were fetched on the current day.
  3. If the fresh data is available, retrieve the pickup points using the findPickupPointsByProviderAndCacheKey($provider, $cache_key) method.
  4. Otherwise:
    • Request a pickup points list from API.
    • Convert each item in the response into a PickupPoint object by creating an instance and populating its properties via the corresponding setters.
    • Save the resulting list of PickupPoint objects to the cache table using the setPickupPointsByCacheKey($pickup_points, $cache_key) method.
    • Record new data to the synchronization state table using the setPickupPointSyncState() method.

New functions

  • Tygh\Tools\Math::roundToPrecisionString($x, $decimals) — rounds the given number to the specified number of decimal places and returns the result as a string.
  • Tygh\Tools\Math::floorFallback($x, $precision) — floors the given number to the given precision using native PHP arithmetic (fallback implementation).
  • Tygh\Tools\Math::ceilFallback($x, $precision) — ceils the given number to the given precision using native PHP arithmetic (fallback implementation).
  • fn_get_storefront_theme_manifest_features — returns theme manifest “features” for the active storefront theme (cached per request).
  • fn_is_storefront_theme_manifest_feature_enabled($feature) — checks whether a boolean feature under theme manifest.json “features” is enabled (strict JSON true).

Changed functions

- function fn_set_storage_data($key, $data = '', $allow_empty = false)
+ function fn_set_storage_data($key, $data = '', $allow_empty = false, $expires_at = null)

Hook changes

New hooks

  • fn_set_hook('apply_emails_and_documents_logo_thumbnails_pre', $logos, $width, $height, $keep_original_format); - Executes before emails and documents logo thumbnails are applied. Allows modifying the original logos list and thumbnail settings.
  • fn_set_hook('apply_emails_and_documents_logo_thumbnails_post', $width, $height, $keep_original_format, $logos, $thumbnail_logos); - Executes after emails and documents logo thumbnails are applied. Allows modifying the original logos list and the logos list with thumbnails.
  • fn_set_hook('apply_emails_and_documents_logo_thumbnail_to_image_pre', $image, $width, $height, $keep_original_format, $thumbnail_path); - Executes before a single emails and documents logo thumbnail is generated. Allows modifying the original image data, thumbnail settings, and thumbnail path.
  • fn_set_hook('apply_emails_and_documents_logo_thumbnail_to_image_post', $width, $height, $keep_original_format, $thumbnail_path, $image, $thumbnail_image); - Executes after a single emails and documents logo thumbnail is generated. Allows modifying the original image data and the image data with thumbnail.
  • fn_set_hook('get_pickup_points_for_checkout_pre', $cart, $pickup_points); — Executes before fetching pickup points for checkout. Allows preventing following script by setting your own list of pickup points.
  • fn_set_hook('get_pickup_points_for_checkout_post', $cart, $pickup_points); — Executes after fetching pickup points for checkout. Allows changing data in pickup points list.
  • fn_set_hook('get_checkout_pickup_points_loader_pre', $cart, $loader); — Executes before getting pickup points loader for checkout. Allows setting your own loader.
  • fn_set_hook('get_checkout_pickup_points_loader_post', $cart, $loader); — Executes after getting pickup points loader for checkout. Allows changing loader data.
  • fn_set_hook('get_pickup_points_for_order_management_pre', $cart, $pickup_points); — Executes before fetching pickup points for order management. Allows preventing following script by setting your own list of pickup points.
  • fn_set_hook('get_pickup_points_for_order_management_post', $cart, $pickup_points); — Executes after fetching pickup points for order management. Allows changing data in pickup points list.
  • fn_set_hook('get_order_management_pickup_points_loader_pre', $cart, $loader); — Executes before getting pickup points loader for order management. Allows setting your own loader.
  • fn_set_hook('get_order_management_pickup_points_loader_post', $cart, $loader); — Executes after getting pickup points loader for order management. Allows changing loader data.
  • fn_set_hook('get_pickup_points_for_order_pre', $order_info, $pickup_points); — Executes before fetching pickup points for order. Allows preventing following script by setting your own list of pickup points.
  • fn_set_hook('get_pickup_points_for_order_post', $order_info, $pickup_points); — Executes after fetching pickup points for order. Allows changing data in pickup points list.
  • fn_set_hook('get_order_pickup_points_loader_pre', $order_info, $loader); — Executes before getting pickup points loader for order. Allows setting your own loader.
  • fn_set_hook('get_order_pickup_points_loader_post', $order_info, $loader); — Executes after getting pickup points loader for order. Allows changing loader data.
  • fn_set_hook('pickup_points_filter_by_q_get_searchable_fields_pre', $searchable_fields); - Executes before filtering pickup points by search query in API. Allows extending the list of searchable fields.
  • fn_set_hook('pickup_points_filter_points_by_q_post', $points, $q, $match); - Executes after filtering a flat list of pickup points by search query in API. Allows changing filtered pickup points data.
  • fn_set_hook('pickup_points_paginate_pre', $pickup_points, $page, $items_per_page); - Executes before paginating pickup points in API. Allows reordering or filtering the list and adjusting pagination params.
  • fn_set_hook('pickup_points_paginate_post', $pickup_points, $page, $items_per_page, $total_items, $total_pages, $items, $params); - Executes after paginating pickup points in API. Allows changing the page slice or pagination metadata.
  • fn_set_hook('get_storefront_theme_manifest_features_post', $features); — Executes after manifest “features” are loaded for the active storefront theme.

Changed hooks

- fn_set_hook('get_store_locations_for_shipping_before_select', $destination_id, $fields, $joins, $conditions, \Tygh\Shippings\Services\StoreLocator $this);
+ fn_set_hook('get_store_locations_for_shipping_before_select', $destination_id, $fields, $joins, $conditions, \Tygh\Shippings\PickupPoints\StoreLocatorPickupPointsByLocationLoader $this);

Addons changes

Hook changes

New hooks

  • fn_set_hook('seo_get_schema_org_organization_markup_post', $storefront, $extra, $organization); — Executes when building Organization Schema.org JSON-LD for the storefront homepage, before filtering in the caller.
  • fn_set_hook('seo_get_schema_org_breadcrumb_list_markup_post', $breadcrumbs, $markup); — Executes when building BreadcrumbList JSON-LD from storefront breadcrumbs, before filtering and output.

Storefront

Image Panel Component for Placing Elements on Product Images

A new Image Panel component is available for placing buttons and other elements on top of a product image. These buttons are now displayed in the top-right corner of the product image by default. Use the Image Panel when you need to add your own buttons or controls on a product image.

How it works

Image Panel uses a data-driven API: you describe elements as items in a Smarty array instead of writing overlay HTML by hand. The component handles layout, positioning, sorting, and rendering.

  1. common/product_data.tpl captures Image Panel output when show_image_panel is enabled.
  2. views/products/components/image_panel_data.tpl builds the $image_panel_items array and exposes the products:image_panel hook for extensions.
  3. views/products/components/image_panel_process.tpl sorts items within each corner.
  4. views/products/components/image_panel.tpl renders the panel.

Each item is placed in one of four corners:

  • top_left
  • top_right
  • bottom_left
  • bottom_right

Extending Image Panel from an Add-on

Add items to $image_panel_items in the products:image_panel template hook.

Example

design/themes/responsive/templates/addons/wishlist/hooks/products/image_panel.post.tpl

{strip}
{if !$hide_wishlist_button && $show_wishlist_button_on_image_panel}
    {$image_panel_items.top_right.add_to_wishlist = [
        type => "a",
        insert_before_id => "add_to_compare",
        id => "button_wishlist_`$obj_prefix``$product.product_id`",
        class => "ty-btn ty-btn__tertiary ty-btn-icon ty-add-to-wish cm-submit",
        title => __("add_to_wishlist"),
        icon => "ty-icon-heart",
        element_attrs => [
            "data-ca-dispatch" => "dispatch[wishlist.add..`$product.product_id`]"
        ]
    ]}
{/if}

{$image_panel_items = $image_panel_items scope=parent}
{/strip}

Important

  • Always export the modified array back to the parent scope: {$image_panel_items = $image_panel_items scope=parent}.
  • Use a unique item key (for example, add_to_wishlist). This key is used for ordering and CSS class generation.
  • Check visibility flags such as $show_wishlist_button_on_image_panel and $hide_wishlist_button before adding an item.

Item data structure

Add items to $image_panel_items using corner keys. Each corner contains an associative array of items.

Common fields

Field Description
type Element type: button, a, or div. If omitted, a is used when href is set; otherwise div is used.
id HTML id attribute.
name HTML name attribute.
title HTML title attribute.
href Link URL. Processed through fn_url.
class CSS classes for the element.
text Text content inside the element.
icon Icon name passed to common/icon.tpl.
icon_class Additional icon CSS classes.
icon_id Icon id attribute.
icon_title Icon title attribute.
icon_text Icon text content.
icon_data Additional icon data array.
ajax When true, adds the cm-ajax class.
full_render When true, adds the cm-ajax-full-render class.
element_attrs Associative array of extra HTML attributes (rel, data-ca-target-id, etc.).
wrapper_attrs Associative array of attributes for the item wrapper.
content HTML content.
insert_before_id Item key to insert this item before.
insert_after_id Item key to insert this item after.
file Custom template file path. When set, the component includes this file instead of rendering the item automatically.

Custom template rendering

If an item needs non-standard markup, set the file parameter and create a template that receives $item and $item_id:

{$image_panel_items.top_left.my_addon_button = [
    file => "addons/my_addon/views/products/components/my_image_panel_button.tpl"
]}

design/themes/responsive/templates/addons/my_addon/views/products/components/my_image_panel_button.tpl

{* $item and $item_id are provided by Image Panel *}
<div class="my-addon-image-panel-button">
    {* custom markup *}
</div>