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

Core Changes

Upcoming PHP version requirements

We plan to raise the minimal supported PHP version in upcoming releases:

  • 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 Supporting Pickup Point Selection

A new interface, TyghShippingsIPickupPointsService, 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 TyghShippingsPickupPointsDtoPickupPoint class.

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

Why? There is a need to standardize pickup point data, which allows:

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

For these reasons, the new TyghShippingsIPickupPointsService 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 TyghShippingsPickupPointsLoaderByLocationIPickupPointsLoaderByLocation interface.
  2. The IPickupPointsLoaderByLocation, in its turn, 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 TyghShippingsPickupPointsDtoPickupPoint object.
  3. Next, the core automatically detects whether 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. Otherwise, they will not.

What does the PickupPoint object contain?

The TyghShippingsPickupPointsDtoPickupPoint 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 TyghShippingsPickupPointsRepository.

  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

  • fn_get_storefront_theme_manifest_features — Theme manifest “features” for the active storefront theme (cached per request).
  • fn_is_storefront_theme_manifest_feature_enabled($feature) — Whether a boolean feature under theme manifest.json “features” is enabled (strict JSON true).

Hook changes

New hooks

  • 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('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);

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)

Addons 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>