Skip to content

Shopping list#2969

Draft
adriendupuis wants to merge 88 commits into5.0from
shopping-list
Draft

Shopping list#2969
adriendupuis wants to merge 88 commits into5.0from
shopping-list

Conversation

@adriendupuis
Copy link
Contributor

@adriendupuis adriendupuis commented Dec 3, 2025

Question Answer
JIRA Ticket IBX-5671
Versions 5.0
Edition Commerce

Document the Commerce's Shopping List LTS Update feature for developers.

Previews:

Related PRs:

  • REST API Ref in-code doc:
    • ibexa/shopping-list#25
    • ibexa/cart#158
  • "Add to shopping list" widget depends on this rework: ibexa/shopping-list#48
  • Fixed charset on SQL dump from YAML schema doctrine-schema#38 to avoid foreign key issue when using ibexa:doctrine:schema:dump-sql to import the schema
  • ibexa/shopping-list#49 about Ibexa\Contracts\ShoppingList\Value\Query\CriterionInterface usage

Not started:

  • JS API & something about visual customization

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

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Preview of modified files

Preview of modified Markdown:

Preview of addition to PHP API Reference:

Comment on lines 92 to 150
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be too much and not essential as anyone can dump it in the console.

!!    OUT  Module build failed: Module not found:
!!    OUT  "./vendor/ibexa/shopping-list/src/bundle/Resources/public/js/component/add.to.shopping.list.ts" contains a reference to the file "@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/dom.helper".
!!    OUT  This file can not be found, please check it for typos or update it if the file got moved.
!!    OUT
!!    OUT  "./vendor/ibexa/shopping-list/src/bundle/Resources/public/js/component/shopping.lists.list.ts" contains a reference to the file "@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/text.helper".
!!    OUT  This file can not be found, please check it for typos or update it if the file got moved.
@adriendupuis adriendupuis requested a review from ViniTou February 18, 2026 13:52
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
132 Security Hotspots
52.7% Duplication on New Code (required ≤ 3%)
E 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

@github-actions
Copy link

code_samples/ change report

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

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@26:``` ts
docs/commerce/shopping_list/shopping_list_design.md@27:[[= 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@28:```

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@63:``` yaml hl_lines="7 8"
docs/commerce/shopping_list/shopping_list_design.md@64:[[= include_file('code_samples/shopping_list/add_to_shopping_list/config/packages/views.yaml') =]]
docs/commerce/shopping_list/shopping_list_design.md@65:```

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@58:``` php hl_lines="24-30"
docs/commerce/shopping_list/shopping_list_design.md@59:[[= include_file('code_samples/shopping_list/add_to_shopping_list/src/Controller/ProductViewController.php') =]]
docs/commerce/shopping_list/shopping_list_design.md@60:```

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 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@68:``` twig hl_lines="7 8 16-18 31-33 44"
docs/commerce/shopping_list/shopping_list_design.md@69:[[= 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@70:```

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⫶ {{ ibexa_content_name(content) }}
013⫶ {{ product.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⫶ {{ variant.name }}
028⫶ {{ variant.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⫶ <button
050⫶ 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;">
054⫶ {{ component }}
055⫶ </div>
056⫶{% 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@31:``` js hl_lines="3-12"
docs/commerce/shopping_list/shopping_list_design.md@32:[[= include_file('code_samples/shopping_list/add_to_shopping_list/webpack.config.js', 43) =]]
docs/commerce/shopping_list/shopping_list_design.md@33:```




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@85:``` yaml
docs/commerce/shopping_list/install_shopping_list.md@86:[[= 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@87:```

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/ShoppingListMoveCommand.php


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

docs/commerce/shopping_list/shopping_list_api.md@87:```php
docs/commerce/shopping_list/shopping_list_api.md@88:[[= include_file('code_samples/shopping_list/php_api/src/Command/ShoppingListMoveCommand.php', 42, 56) =]]
docs/commerce/shopping_list/shopping_list_api.md@89:```

001⫶ $entriesToMove = [];
002⫶ $entriesToRemove = [];
003⫶ foreach ($movedProductCodes as $productCode) {
004⫶ if ($sourceList->getEntries()->hasEntryWithProductCode($productCode)) {
005⫶ if ($targetList->getEntries()->hasEntryWithProductCode($productCode)) {
006⫶ $entriesToRemove[] = $sourceList->getEntries()->getEntryWithProductCode($productCode);
007⫶ } else {
008⫶ $entriesToMove[] = $sourceList->getEntries()->getEntryWithProductCode($productCode);
009⫶ }
010⫶ }
011⫶ }
012⫶ $this->shoppingListService->moveEntries($targetList, $entriesToMove);
013⫶ $targetList = $this->shoppingListService->getShoppingList($targetList->getIdentifier()); // Refresh local object from persistence
014⫶ $sourceList = $this->shoppingListService->removeEntries($sourceList, $entriesToRemove); // Refresh local object from persistence even if $entriesToRemove is empty


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@100:```php
docs/commerce/shopping_list/shopping_list_api.md@101:[[= include_file('code_samples/shopping_list/php_api/src/Controller/CartShoppingListTransferController.php', 69, 90) =]]
docs/commerce/shopping_list/shopping_list_api.md@102:```

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


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@130:```bash
docs/commerce/shopping_list/shopping_list_api.md@131:[[= include_file('code_samples/shopping_list/shopping_list_rest_api.sh', 5) =]]
docs/commerce/shopping_list/shopping_list_api.md@132:```

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments