Skip to content

V5.0.6#3069

Merged
adriendupuis merged 276 commits into5.0from
v5.0.6
Mar 5, 2026
Merged

V5.0.6#3069
adriendupuis merged 276 commits into5.0from
v5.0.6

Conversation

@adriendupuis
Copy link
Contributor

@adriendupuis adriendupuis commented Mar 3, 2026

Question Answer
JIRA Ticket N/A
Versions 5.0
Edition N/A
PR 4.6 5.0 LUp Eds Section Sub-section
#2969 x x C
#2985 x HEC Infrastructure Ibexa Cloud package
#3067 x x HEC Infrastructure PHP 8.4 support
#3044 x HEC Query subtree limit configuration
#3033 (x) x EC Improved HTTP caching for Page Builder and dashboard blocks
#3045 x C Improved product variant querying
#3072 x HEC Developer experience API Refs v4.6.28
#3074 x HEC Developer experience API Refs v5.0.6

Also include #3066

Checklist

  • Text renders correctly
  • Text has been checked with vale
  • Description metadata is up to date
  • Redirects cover removed/moved pages
  • Code samples are working
  • PHP code samples have been fixed with PHP CS fixer
  • Added link to this PR in relevant JIRA ticket or code PR

adriendupuis and others added 30 commits January 28, 2026 15:40
Twig comments are not rendered when directly in the Markdown, moving this code to an included file fix the issue.
Co-authored-by: Marek Nocoń <mnocon@users.noreply.github.com>
…ield.md

Co-authored-by: Bartek Wajda <bartlomiej.wajda@ibexa.co>
# Conflicts:
#	docs/templating/twig_function_reference/icon_twig_functions.md
@adriendupuis adriendupuis marked this pull request as ready for review March 5, 2026 10:01
… into v5.0.6"

This reverts commit 4be5fcc.

Conflicts:
	docs/release_notes/ibexa_dxp_v4.6.md
	docs/release_notes/ibexa_dxp_v5.0.md

[[= release_note_entry_begin(
'Shopping Lists ' + version,
'YYYY-MM-DD',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'YYYY-MM-DD',
'2026-03-05',


[[= release_note_entry_begin(
"Ibexa DXP " + version,
'YYYY-MM-DD',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'YYYY-MM-DD',
'2026-03-05',

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/api/product_catalog/src/Command/ProductVariantCommand.php


code_samples/api/product_catalog/src/Command/ProductVariantCommand.php

docs/pim/product_api.md@67:``` php
docs/pim/product_api.md@68:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 49, 52) =]]
docs/pim/product_api.md@69:```

001⫶ $variantQuery = new ProductVariantQuery(0, 5);
002⫶
003⫶ $variants = $this->productService->findProductVariants($product, $variantQuery)->getVariants();

docs/pim/product_api.md@73:``` php
docs/pim/product_api.md@74:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 53, 60) =]]
docs/pim/product_api.md@75:```

001⫶ foreach ($variants as $variant) {
002⫶ $output->writeln($variant->getName());
003⫶ $attributes = $variant->getDiscriminatorAttributes();
004⫶ foreach ($attributes as $attribute) {
005⫶ $output->writeln($attribute->getIdentifier() . ': ' . $attribute->getValue() . ' ');
006⫶ }
007⫶ }

docs/pim/product_api.md@83:``` php
docs/pim/product_api.md@84:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 62, 68) =]]
docs/pim/product_api.md@85:```

001⫶ $variantCreateStructs = [
002⫶ new ProductVariantCreateStruct(['color' => 'oak', 'frame_color' => 'white'], 'DESK1'),
003⫶ new ProductVariantCreateStruct(['color' => 'white', 'frame_color' => 'black'], 'DESK2'),
004⫶ ];
005⫶
006⫶ $this->localProductService->createProductVariants($product, $variantCreateStructs);
docs/pim/product_api.md@70:    ``` php
docs/pim/product_api.md@71:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 50, 54) =]]
docs/pim/product_api.md@72: ```

001⫶ // Get variants filtered by variant codes
002⫶ $codeQuery = new ProductVariantQuery();
003⫶ $codeQuery->setVariantCodes(['DESK-red', 'DESK-blue']);
004⫶ $specificVariants = $this->productService->findProductVariants($product, $codeQuery)->getVariants();

docs/pim/product_api.md@78: ``` php hl_lines="4"
docs/pim/product_api.md@79:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 55, 66) =]]
docs/pim/product_api.md@80: ```

001⫶ // Get variants with specific attributes
002⫶ $combinedQuery = new ProductVariantQuery();
003⫶ $combinedQuery->setAttributesCriterion(
004❇️ new ProductCriterionAdapter(
005⫶ new Criterion\LogicalAnd([
006⫶ new Criterion\ColorAttribute('color', ['red', 'blue']),
007⫶ new Criterion\IntegerAttribute('size', 42),
008⫶ ])
009⫶ )
010⫶ );
011⫶ $filteredVariants = $this->productService->findProductVariants($product, $combinedQuery)->getVariants();

docs/pim/product_api.md@84:``` php
docs/pim/product_api.md@85:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 69, 73) =]]
docs/pim/product_api.md@86:```

001⫶ $attributes = $variant->getDiscriminatorAttributes();
002⫶ foreach ($attributes as $attribute) {
003⫶ $output->writeln($attribute->getIdentifier() . ': ' . $attribute->getValue() . ' ');
004⫶ }

docs/pim/product_api.md@99: ``` php
docs/pim/product_api.md@100:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 83, 87) =]]
docs/pim/product_api.md@101: ```

001⫶ // Search variants across all products
002⫶ $query = new ProductVariantQuery();
003⫶ $query->setVariantCodes(['DESK-red', 'DESK-blue']);
004⫶ $variantList = $this->productService->findVariants($query);

docs/pim/product_api.md@107: ``` php hl_lines="4"
docs/pim/product_api.md@108:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 92, 100) =]]
docs/pim/product_api.md@109: ```

001⫶ // Search variants with attribute criterion
002⫶ $colorQuery = new ProductVariantQuery();
003⫶ $colorQuery->setAttributesCriterion(
004❇️ new ProductCriterionAdapter(
005⫶ new Criterion\ColorAttribute('color', ['red'])
006⫶ )
007⫶ );
008⫶ $redVariants = $this->productService->findVariants($colorQuery);

docs/pim/product_api.md@117:``` php
docs/pim/product_api.md@118:[[= include_file('code_samples/api/product_catalog/src/Command/ProductVariantCommand.php', 85, 91) =]]
docs/pim/product_api.md@119:```

001⫶ $query->setVariantCodes(['DESK-red', 'DESK-blue']);
002⫶ $variantList = $this->productService->findVariants($query);
003⫶
004⫶ foreach ($variantList->getVariants() as $variant) {
005⫶ $output->writeln($variant->getName());
006⫶ }


code_samples/page/pagefield_layout.html.twig



code_samples/page/pagefield_layout.html.twig

docs/content_management/field_types/field_type_reference/pagefield.md@72:``` html+twig
docs/content_management/field_types/field_type_reference/pagefield.md@73:[[= include_file('code_samples/page/pagefield_layout.html.twig') =]]
docs/content_management/field_types/field_type_reference/pagefield.md@74:```

001⫶<div>
002⫶ {# The required attribute for the displayed zone #}
003⫶ <div data-ibexa-zone-id="{{ zones[0].id }}">
004⫶ {# If a zone with [0] index contains any blocks #}
005⫶ {% if zones[0].blocks %}
006⫶ {# for each block #}
007⫶ {% for block in blocks %}
008⫶ {# create a new layer with appropriate ID #}
009⫶ <div class="landing-page__block block_{{ block.type }}" data-ibexa-block-id="{{ block.id }}">
010⫶ {# render the block by using the "Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction" controller #}
011⫶ {# location.id is the ID of the Location of the current content item, block.id is the ID of the current block #}
012⫶ {{ render_esi(controller('Ibexa\\Bundle\\FieldTypePage\\Controller\\BlockController::renderAction', {
013⫶ 'locationId': locationId,
014⫶ 'blockId': block.id,
015⫶ 'versionNo': versionInfo.versionNo,
016⫶ 'languageCode': field.languageCode
017⫶ }, ibexa_append_cacheable_query_params(block))) }}
018⫶ </div>
019⫶ {% endfor %}
020⫶ {% endif %}
021⫶ </div>
022⫶</div>


code_samples/shopping_list/add_to_shopping_list/assets/js/add-to-shopping-list.ts


code_samples/shopping_list/add_to_shopping_list/assets/js/add-to-shopping-list.ts

docs/commerce/shopping_list/shopping_list_design.md@33:``` ts
docs/commerce/shopping_list/shopping_list_design.md@34:[[= include_file('code_samples/shopping_list/add_to_shopping_list/assets/js/add-to-shopping-list.ts') =]]
docs/commerce/shopping_list/shopping_list_design.md@35:```

001⫶// Shopping list service
002⫶import ShoppingList from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/shopping.list';
003⫶// The Add to shopping list interaction
004⫶import { AddToShoppingList } from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/add.to.shopping.list';
005⫶// List of all user's shopping lists
006⫶import { ShoppingListsList } from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/shopping.lists.list';
007⫶
008⫶(function (global: Window, doc: Document) {
009⫶ const shoppingList = new ShoppingList();
010⫶ shoppingList.init(); // Fetch user's shopping lists
011⫶
012⫶ const addToShoppingListsNodes = doc.querySelectorAll<HTMLDivElement>('.ibexa-sl-add-to-shopping-list');
013⫶ addToShoppingListsNodes.forEach((addToShoppingListNode) => {
014⫶ const addToShoppingList = new AddToShoppingList({ node: addToShoppingListNode, ListClass: ShoppingListsList });
015⫶
016⫶ addToShoppingList.init();
017⫶ });
018⫶})(window, window.document);


code_samples/shopping_list/add_to_shopping_list/config/packages/views.yaml


code_samples/shopping_list/add_to_shopping_list/config/packages/views.yaml

docs/commerce/shopping_list/shopping_list_design.md@78:``` yaml hl_lines="7 8"
docs/commerce/shopping_list/shopping_list_design.md@79:[[= include_file('code_samples/shopping_list/add_to_shopping_list/config/packages/views.yaml') =]]
docs/commerce/shopping_list/shopping_list_design.md@80:```

001⫶ibexa:
002⫶ system:
003⫶ default:
004⫶ content_view:
005⫶ full:
006⫶ product:
007❇️ controller: 'App\Controller\ProductViewController::viewAction'
008❇️ template: '@ibexadesign/full/product.html.twig'
009⫶ match:
010⫶ '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsProduct': true


code_samples/shopping_list/add_to_shopping_list/src/Controller/ProductViewController.php


code_samples/shopping_list/add_to_shopping_list/src/Controller/ProductViewController.php

docs/commerce/shopping_list/shopping_list_design.md@67:``` php hl_lines="24-30"
docs/commerce/shopping_list/shopping_list_design.md@68:[[= include_file('code_samples/shopping_list/add_to_shopping_list/src/Controller/ProductViewController.php') =]]
docs/commerce/shopping_list/shopping_list_design.md@69:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use Ibexa\Contracts\Core\Repository\Iterator\BatchIterator;
006⫶use Ibexa\Contracts\ProductCatalog\Iterator\BatchIteratorAdapter\ProductVariantFetchAdapter;
007⫶use Ibexa\Contracts\ProductCatalog\Local\LocalProductServiceInterface;
008⫶use Ibexa\Contracts\ProductCatalog\Values\Product\ProductVariantQuery;
009⫶use Ibexa\Core\MVC\Symfony\View\ContentView;
010⫶use Ibexa\Core\MVC\Symfony\View\View;
011⫶use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
012⫶use Symfony\Component\HttpFoundation\Request;
013⫶
014⫶class ProductViewController extends AbstractController
015⫶{
016⫶ public function __construct(private readonly LocalProductServiceInterface $productService)
017⫶ {
018⫶ }
019⫶
020⫶ public function viewAction(Request $request, ContentView $view): View
021⫶ {
022⫶ $product = $this->productService->getProductFromContent($view->getContent());
023⫶ if ($product->isBaseProduct()) {
024❇️ $view->addParameters([
025❇️ 'variants' => new BatchIterator(new ProductVariantFetchAdapter(
026❇️ $this->productService,
027❇️ $product,
028❇️ new ProductVariantQuery(),
029❇️ )),
030❇️ ]);
031⫶ }
032⫶
033⫶ return $view;
034⫶ }
035⫶}


code_samples/shopping_list/add_to_shopping_list/templates/themes/standard/full/product.html.twig


code_samples/shopping_list/add_to_shopping_list/templates/themes/standard/full/product.html.twig

docs/commerce/shopping_list/shopping_list_design.md@72:``` twig hl_lines="7 8 16-18 31-33 44"
docs/commerce/shopping_list/shopping_list_design.md@73:[[= include_file('code_samples/shopping_list/add_to_shopping_list/templates/themes/standard/full/product.html.twig') =]]
docs/commerce/shopping_list/shopping_list_design.md@74:```

001⫶{% extends '@ibexadesign/pagelayout.html.twig' %}
002⫶
003⫶{% set product = content|ibexa_get_product %}
004⫶
005⫶{% block meta %}
006⫶ {% set token = csrf_token ?? csrf_token(ibexa_get_rest_csrf_token_intention()) %}
007❇️ <meta name="CSRF-Token" content="{{ token }}"/>
008❇️ <meta name="SiteAccess" content="{{ app.request.get('siteaccess').name }}"/>
009⫶{% endblock %}
010⫶
011⫶{% block content %}
012⫶ <span>{{ ibexa_content_name(content) }}</span>
013⫶ <code>{{ product.code }}</code>
014⫶ {% if not product.isBaseProduct() and can_view_shopping_list and can_edit_shopping_list %}
015⫶ {% set component %}
016❇️ {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with {
017❇️ product_code: product.code,
018❇️ } %}
019⫶ {% endset %}
020⫶ {{ _self.add_to_shopping_list(product, component) }}
021⫶ {% endif %}
022⫶
023⫶ {% if product.isBaseProduct() %}
024⫶ <ul>
025⫶ {% for variant in variants %}
026⫶ <li>
027⫶ <span>{{ variant.name }}</span>
028⫶ <code>{{ variant.code }}</code>
029⫶ {% if can_view_shopping_list and can_edit_shopping_list %}
030⫶ {% set component %}
031❇️ {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with {
032❇️ product_code: variant.code,
033❇️ } %}
034⫶ {% endset %}
035⫶ {{ _self.add_to_shopping_list(variant, component) }}
036⫶ {% endif %}
037⫶ </li>
038⫶ {% endfor %}
039⫶ </ul>
040⫶ {% endif %}
041⫶{% endblock %}
042⫶
043⫶{% block javascripts %}
044❇️ {{ encore_entry_script_tags('add-to-shopping-list-js') }}
045⫶{% endblock %}
046⫶
047⫶{% macro add_to_shopping_list(product, component) %}
048⫶ {% set widget_id = 'add-to-shopping-list-' ~ product.code|slug %}
049⫶ <div style="display: inline-block;">
050⫶ <button onclick="(function(){let e=document.getElementById('{{ widget_id }}'); e.style.display=('none'===window.getComputedStyle(e).display)?'block':'none';})()">
051⫶ Add to shopping list
052⫶ </button>
053⫶ <div id="{{ widget_id }}" style="display: none; position: absolute; background: #fff;">
054⫶ {{ component }}
055⫶ </div>
056⫶ </div>
057⫶{% endmacro %}


code_samples/shopping_list/add_to_shopping_list/webpack.config.js


code_samples/shopping_list/add_to_shopping_list/webpack.config.js

docs/commerce/shopping_list/shopping_list_design.md@38:``` js hl_lines="5-14"
docs/commerce/shopping_list/shopping_list_design.md@39:// […]
docs/commerce/shopping_list/shopping_list_design.md@40:
docs/commerce/shopping_list/shopping_list_design.md@41:[[= include_file('code_samples/shopping_list/add_to_shopping_list/webpack.config.js', 43) =]]
docs/commerce/shopping_list/shopping_list_design.md@42:```

001⫶// […]


code_samples/shopping_list/install/schema.mysql.sql


code_samples/shopping_list/install/schema.mysql.sql

docs/commerce/shopping_list/install_shopping_list.md@41:    ```sql
docs/commerce/shopping_list/install_shopping_list.md@42: [[= include_file('code_samples/shopping_list/install/schema.mysql.sql', 0, None, ' ') =]]
docs/commerce/shopping_list/install_shopping_list.md@43: ```

001⫶ CREATE TABLE ibexa_shopping_list (
002⫶ id INT AUTO_INCREMENT NOT NULL,
003⫶ owner_id INT NOT NULL,
004⫶ identifier CHAR(36) NOT NULL COMMENT '(DC2Type:guid)',
005⫶ name VARCHAR(190) DEFAULT NULL,
006⫶ created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)',
007⫶ updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)',
008⫶ is_default TINYINT(1) DEFAULT 0 NOT NULL,
009⫶ UNIQUE INDEX ibexa_shopping_list_identifier_idx (identifier),
010⫶ INDEX ibexa_shopping_list_owner_idx (owner_id),
011⫶ INDEX ibexa_shopping_list_default_idx (is_default),
012⫶ PRIMARY KEY(id)
013⫶ ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB;
014⫶ CREATE TABLE ibexa_shopping_list_entry (
015⫶ id INT AUTO_INCREMENT NOT NULL,
016⫶ shopping_list_id INT NOT NULL,
017⫶ product_code VARCHAR(64) NOT NULL,
018⫶ identifier CHAR(36) NOT NULL COMMENT '(DC2Type:guid)',
019⫶ added_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)',
020⫶ UNIQUE INDEX ibexa_shopping_list_entry_identifier_idx (identifier),
021⫶ INDEX ibexa_shopping_list_entry_list_idx (shopping_list_id),
022⫶ INDEX ibexa_shopping_list_entry_product_idx (product_code),
023⫶ INDEX ibexa_shopping_list_entry_added_at_idx (added_at),
024⫶ UNIQUE INDEX ibexa_shopping_list_entry_unique (shopping_list_id, product_code),
025⫶ PRIMARY KEY(id)
026⫶ ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB;
027⫶ ALTER TABLE ibexa_shopping_list
028⫶ ADD CONSTRAINT ibexa_shopping_list_owner_fk FOREIGN KEY (owner_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE;
029⫶ ALTER TABLE ibexa_shopping_list_entry
030⫶ ADD CONSTRAINT ibexa_shopping_list_entry_list_fk FOREIGN KEY (shopping_list_id) REFERENCES ibexa_shopping_list (id) ON UPDATE CASCADE ON DELETE CASCADE;
031⫶ ALTER TABLE ibexa_shopping_list_entry
032⫶ ADD CONSTRAINT ibexa_shopping_list_entry_product_fk FOREIGN KEY (product_code) REFERENCES ibexa_product (code) ON UPDATE CASCADE ON DELETE CASCADE;


code_samples/shopping_list/install/schema.postgresql.sql


code_samples/shopping_list/install/schema.postgresql.sql

docs/commerce/shopping_list/install_shopping_list.md@47:    ```sql
docs/commerce/shopping_list/install_shopping_list.md@48: [[= include_file('code_samples/shopping_list/install/schema.postgresql.sql', 0, None, ' ') =]]
docs/commerce/shopping_list/install_shopping_list.md@49: ```

001⫶ CREATE TABLE ibexa_shopping_list (
002⫶ id SERIAL NOT NULL,
003⫶ owner_id INT NOT NULL,
004⫶ identifier UUID NOT NULL,
005⫶ name VARCHAR(190) DEFAULT NULL,
006⫶ created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
007⫶ updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
008⫶ is_default BOOLEAN DEFAULT false NOT NULL,
009⫶ PRIMARY KEY (id)
010⫶ );
011⫶ CREATE UNIQUE INDEX ibexa_shopping_list_identifier_idx ON ibexa_shopping_list (identifier);
012⫶ CREATE INDEX ibexa_shopping_list_owner_idx ON ibexa_shopping_list (owner_id);
013⫶ CREATE INDEX ibexa_shopping_list_default_idx ON ibexa_shopping_list (is_default);
014⫶ COMMENT ON COLUMN ibexa_shopping_list.created_at IS '(DC2Type:datetime_immutable)';
015⫶ COMMENT ON COLUMN ibexa_shopping_list.updated_at IS '(DC2Type:datetime_immutable)';
016⫶ CREATE TABLE ibexa_shopping_list_entry (
017⫶ id SERIAL NOT NULL,
018⫶ shopping_list_id INT NOT NULL,
019⫶ product_code VARCHAR(64) NOT NULL,
020⫶ identifier UUID NOT NULL,
021⫶ added_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
022⫶ PRIMARY KEY (id)
023⫶ );
024⫶ CREATE UNIQUE INDEX ibexa_shopping_list_entry_identifier_idx ON ibexa_shopping_list_entry (identifier);
025⫶ CREATE INDEX ibexa_shopping_list_entry_list_idx ON ibexa_shopping_list_entry (shopping_list_id);
026⫶ CREATE INDEX ibexa_shopping_list_entry_product_idx ON ibexa_shopping_list_entry (product_code);
027⫶ CREATE INDEX ibexa_shopping_list_entry_added_at_idx ON ibexa_shopping_list_entry (added_at);
028⫶ CREATE UNIQUE INDEX ibexa_shopping_list_entry_unique ON ibexa_shopping_list_entry (shopping_list_id, product_code);
029⫶ COMMENT ON COLUMN ibexa_shopping_list_entry.added_at IS '(DC2Type:datetime_immutable)';
030⫶ ALTER TABLE ibexa_shopping_list
031⫶ ADD CONSTRAINT ibexa_shopping_list_owner_fk FOREIGN KEY (owner_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
032⫶ ALTER TABLE ibexa_shopping_list_entry
033⫶ ADD CONSTRAINT ibexa_shopping_list_entry_list_fk FOREIGN KEY (shopping_list_id) REFERENCES ibexa_shopping_list (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
034⫶ ALTER TABLE ibexa_shopping_list_entry
035⫶ ADD CONSTRAINT ibexa_shopping_list_entry_product_fk FOREIGN KEY (product_code) REFERENCES ibexa_product (code) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;


code_samples/shopping_list/install/src/Migrations/Ibexa/migrations/shopping_list_user.yaml


code_samples/shopping_list/install/src/Migrations/Ibexa/migrations/shopping_list_user.yaml

docs/commerce/shopping_list/install_shopping_list.md@86:``` yaml
docs/commerce/shopping_list/install_shopping_list.md@87:[[= include_file('code_samples/shopping_list/install/src/Migrations/Ibexa/migrations/shopping_list_user.yaml', 4, 29) =]]
docs/commerce/shopping_list/install_shopping_list.md@88:```

001⫶- type: role
002⫶ mode: create
003⫶ metadata:
004⫶ identifier: Shopping List User
005⫶ policies:
006⫶ - module: shopping_list
007⫶ function: create
008⫶ limitations:
009⫶ - identifier: ShoppingListOwner
010⫶ values: [self]
011⫶ - module: shopping_list
012⫶ function: view
013⫶ limitations:
014⫶ - identifier: ShoppingListOwner
015⫶ values: [self]
016⫶ - module: shopping_list
017⫶ function: edit
018⫶ limitations:
019⫶ - identifier: ShoppingListOwner
020⫶ values: [self]
021⫶ - module: shopping_list
022⫶ function: delete
023⫶ limitations:
024⫶ - identifier: ShoppingListOwner
025⫶ values: [self]


code_samples/shopping_list/php_api/src/Command/ShoppingListFilterCommand.php


code_samples/shopping_list/php_api/src/Command/ShoppingListFilterCommand.php

docs/commerce/shopping_list/shopping_list_api.md@73:```php
docs/commerce/shopping_list/shopping_list_api.md@74:[[= include_file('code_samples/shopping_list/php_api/src/Command/ShoppingListFilterCommand.php', 39, 50) =]]
docs/commerce/shopping_list/shopping_list_api.md@75:```

001⫶ $filteredProductCodes = array_filter(
002⫶ $desiredProductCodes,
003⫶ static fn ($productCode): bool => !$list->getEntries()->hasEntryWithProductCode($productCode)
004⫶ );
005⫶ $list = $this->shoppingListService->addEntries(
006⫶ $list,
007⫶ array_map(
008⫶ static fn ($productCode): EntryAddStruct => new EntryAddStruct($productCode),
009⫶ $filteredProductCodes
010⫶ )
011⫶ );


code_samples/shopping_list/php_api/src/Command/ShoppingListMoveCommand.php


code_samples/shopping_list/php_api/src/Command/ShoppingListMoveCommand.php

docs/commerce/shopping_list/shopping_list_api.md@79:```php
docs/commerce/shopping_list/shopping_list_api.md@80:[[= include_file('code_samples/shopping_list/php_api/src/Command/ShoppingListMoveCommand.php', 42, 54) =]]
docs/commerce/shopping_list/shopping_list_api.md@81:```

001⫶ $entriesToRemove = [];
002⫶ $entriesToAdd = [];
003⫶ foreach ($movedProductCodes as $productCode) {
004⫶ if ($sourceList->getEntries()->hasEntryWithProductCode($productCode)) {
005⫶ $entriesToRemove[] = $sourceList->getEntries()->getEntryWithProductCode($productCode);
006⫶ if (!$targetList->getEntries()->hasEntryWithProductCode($productCode)) {
007⫶ $entriesToAdd[] = new EntryAddStruct($productCode);
008⫶ }
009⫶ }
010⫶ }
011⫶ $sourceList = $this->shoppingListService->removeEntries($sourceList, $entriesToRemove);
012⫶ $targetList = $this->shoppingListService->addEntries($targetList, $entriesToAdd);


code_samples/shopping_list/php_api/src/Controller/CartShoppingListTransferController.php


code_samples/shopping_list/php_api/src/Controller/CartShoppingListTransferController.php

docs/commerce/shopping_list/shopping_list_api.md@92:```php
docs/commerce/shopping_list/shopping_list_api.md@93:[[= include_file('code_samples/shopping_list/php_api/src/Controller/CartShoppingListTransferController.php', 69, 92) =]]
docs/commerce/shopping_list/shopping_list_api.md@94:```

001⫶ $this->cartService->emptyCart($cart);
002⫶ $list = $this->shoppingListService->clearShoppingList($list);
003⫶
004⫶ $list = $this->shoppingListService->addEntries($list, [new ShoppingListEntryAddStruct($productCode)]);
005⫶
006⫶ $entry = $list->getEntries()->getEntryWithProductCode($productCode)->getIdentifier(); // Get entry's automatically generated identifier
007⫶ $cart = $this->cartShoppingListTransferService->addSelectedEntriesToCart($list, [$entry], $cart);
008⫶ $cart = $this->cartShoppingListTransferService->addSelectedEntriesToCart($list, [$entry], $cart);
009⫶
010⫶ dump(
011⫶ $list->getEntries()->hasEntryWithProductCode($productCode), // true as the entry is copied and not moved
012⫶ $cart->getEntries()->getEntryForProduct($this->productService->getProduct($productCode))->getQuantity() // 2 as the entry was added twice
013⫶ );
014⫶
015⫶ $list = $this->shoppingListService->clearShoppingList($list); // Empty the list to avoid duplicate and test the move from cart
016⫶
017⫶ $list = $this->cartShoppingListTransferService->moveCartToShoppingList($cart, $list);
018⫶ $cart = $this->cartService->getCart($cart->getIdentifier()); // Refresh local object from persistence
019⫶
020⫶ dump(
021⫶ $list->getEntries()->hasEntryWithProductCode($productCode), // true as, after the clear, the entry is moved from cart
022⫶ $cart->getEntries()->hasEntryForProduct($this->productService->getProduct($productCode)) // false as the entry was moved
023⫶ );


code_samples/shopping_list/search/criteria.php


code_samples/shopping_list/search/criteria.php

docs/search/shopping_list_search_reference/shopping_list_criteria.md@32:```php hl_lines="8-9"
docs/search/shopping_list_search_reference/shopping_list_criteria.md@33:[[= include_file('code_samples/shopping_list/search/criteria.php', 2) =]]
docs/search/shopping_list_search_reference/shopping_list_criteria.md@34:```

001⫶use Ibexa\Contracts\ShoppingList\Value\Query;
002⫶use Ibexa\Contracts\ShoppingList\Value\ShoppingListQuery;
003⫶
004⫶/** @var \Ibexa\Contracts\Core\Repository\PermissionResolver $permissionResolver */
005⫶$query = new ShoppingListQuery(
006⫶ new Query\Criterion\LogicalAnd(
007⫶ new Query\Criterion\OwnerCriterion($permissionResolver->getCurrentUserReference()),
008❇️ new Query\Criterion\IsDefaultCriterion(false)
009❇️ ),
010⫶ [
011⫶ new Query\SortClause\Name(),


code_samples/shopping_list/search/sort_clauses.php


code_samples/shopping_list/search/sort_clauses.php

docs/search/shopping_list_search_reference/shopping_list_sort_clauses.md@21:```php hl_lines="11-12"
docs/search/shopping_list_search_reference/shopping_list_sort_clauses.md@22:[[= include_file('code_samples/shopping_list/search/sort_clauses.php', 2) =]]
docs/search/shopping_list_search_reference/shopping_list_sort_clauses.md@23:```

001⫶use Ibexa\Contracts\ShoppingList\Value\Query\SortClause\IsDefault;
002⫶use Ibexa\Contracts\ShoppingList\Value\Query\SortClause\Name;
003⫶use Ibexa\Contracts\ShoppingList\Value\ShoppingListQuery;
004⫶
005⫶/** @var \Ibexa\Contracts\ShoppingList\ShoppingListServiceInterface $shoppingListService */
006⫶$lists = $shoppingListService->findShoppingLists(
007⫶ new ShoppingListQuery(
008⫶ null,
009⫶ [
010⫶ new IsDefault(IsDefault::SORT_DESC),
011❇️ new Name(),
012❇️ ]


code_samples/shopping_list/shopping_list_rest_api.sh


code_samples/shopping_list/shopping_list_rest_api.sh

docs/commerce/shopping_list/shopping_list_api.md@122:```bash
docs/commerce/shopping_list/shopping_list_api.md@123:[[= include_file('code_samples/shopping_list/shopping_list_rest_api.sh', 5) =]]
docs/commerce/shopping_list/shopping_list_api.md@124:```

001⫶# Log in and store CSRF Token
002⫶csrf_token=`curl -s -c cookie.txt -X 'POST' \
003⫶ "$BASE_URL/api/ibexa/v2/user/sessions" \
004⫶ -H 'accept: application/vnd.ibexa.api.Session+json' \
005⫶ -H 'Content-Type: application/vnd.ibexa.api.SessionInput+json' \
006⫶ -d "{
007⫶ \"SessionInput\": {
008⫶ \"login\": \"$CUSTOMER_USERNAME\",
009⫶ \"password\": \"$CUSTOMER_PASSWORD\"
010⫶ }
011⫶}" | jq -r '.Session.csrfToken'`
012⫶
013⫶# Get default shopping list identifier if it exists
014⫶default_list_identifier=`curl -s -b cookie.txt -X 'GET' \
015⫶ "$BASE_URL/api/ibexa/v2/shopping-list?isDefault=true" \
016⫶ -H 'accept: application/vnd.ibexa.api.ShoppingListCollection+json' \
017⫶ | jq -r '.ShoppingListCollection.ShoppingList[0].identifier'`
018⫶
019⫶# Clear default shopping list
020⫶if [ "" != "$default_list_identifier" ]; then
021⫶ curl -s -b cookie.txt -X 'POST' \
022⫶ "$BASE_URL/api/ibexa/v2/shopping-list/$default_list_identifier/clear" \
023⫶ -H 'accept: application/vnd.ibexa.api.ShoppingList+json' \
024⫶ -H "X-CSRF-Token: $csrf_token" | jq
025⫶fi
026⫶
027⫶# Add entries to the default shopping list,
028⫶# create it if it doesn't exist yet,
029⫶# and get the updated data
030⫶curl -s -b cookie.txt -X 'POST' \
031⫶ "$BASE_URL/api/ibexa/v2/shopping-list/default/entries" \
032⫶ -H 'accept: application/vnd.ibexa.api.ShoppingList+json' \
033⫶ -H "X-CSRF-Token: $csrf_token" \
034⫶ -H 'Content-Type: application/vnd.ibexa.api.ShoppingListEntriesAdd+json' \
035⫶ -d "{
036⫶ \"ShoppingListEntriesAdd\": {
037⫶ \"entries\": [
038⫶ {

Download colorized diff

@adriendupuis adriendupuis merged commit e2a6187 into 5.0 Mar 5, 2026
1 check passed
@adriendupuis adriendupuis deleted the v5.0.6 branch March 5, 2026 16:34
@adriendupuis adriendupuis mentioned this pull request Mar 5, 2026
7 tasks
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 5, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
153 Security Hotspots
66.6% Duplication on New Code (required ≤ 3%)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants